Merge pull request #3848 from swagger-api/bug/3847-href-xss
Add URL sanitizer to avoid `href` XSS attack vector
This commit is contained in:
@@ -39,6 +39,7 @@
|
|||||||
"e2e": "npm-run-all --parallel -r hot-server mock-api test-e2e"
|
"e2e": "npm-run-all --parallel -r hot-server mock-api test-e2e"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@braintree/sanitize-url": "^2.0.2",
|
||||||
"base64-js": "^1.2.0",
|
"base64-js": "^1.2.0",
|
||||||
"brace": "0.7.0",
|
"brace": "0.7.0",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react"
|
|||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
import { fromJS } from "immutable"
|
import { fromJS } from "immutable"
|
||||||
import ImPropTypes from "react-immutable-proptypes"
|
import ImPropTypes from "react-immutable-proptypes"
|
||||||
|
import { sanitizeUrl } from "core/utils"
|
||||||
|
|
||||||
|
|
||||||
class Path extends React.Component {
|
class Path extends React.Component {
|
||||||
@@ -35,9 +36,9 @@ class Contact extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{ url && <div><a href={ url } target="_blank">{ name } - Website</a></div> }
|
{ url && <div><a href={ sanitizeUrl(url) } target="_blank">{ name } - Website</a></div> }
|
||||||
{ email &&
|
{ email &&
|
||||||
<a href={`mailto:${email}`}>
|
<a href={sanitizeUrl(`mailto:${email}`)}>
|
||||||
{ url ? `Send email to ${name}` : `Contact ${name}`}
|
{ url ? `Send email to ${name}` : `Contact ${name}`}
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
@@ -59,7 +60,7 @@ class License extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
url ? <a target="_blank" href={ url }>{ name }</a>
|
url ? <a target="_blank" href={ sanitizeUrl(url) }>{ name }</a>
|
||||||
: <span>{ name }</span>
|
: <span>{ name }</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -97,7 +98,7 @@ export default class Info extends React.Component {
|
|||||||
{ version && <VersionStamp version={version}></VersionStamp> }
|
{ version && <VersionStamp version={version}></VersionStamp> }
|
||||||
</h2>
|
</h2>
|
||||||
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
|
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
|
||||||
{ url && <a target="_blank" href={ url }><span className="url"> { url } </span></a> }
|
{ url && <a target="_blank" href={ sanitizeUrl(url) }><span className="url"> { url } </span></a> }
|
||||||
</hgroup>
|
</hgroup>
|
||||||
|
|
||||||
<div className="description">
|
<div className="description">
|
||||||
@@ -106,14 +107,14 @@ export default class Info extends React.Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
termsOfService && <div>
|
termsOfService && <div>
|
||||||
<a target="_blank" href={ termsOfService }>Terms of service</a>
|
<a target="_blank" href={ sanitizeUrl(termsOfService) }>Terms of service</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{ contact && contact.size ? <Contact data={ contact } /> : null }
|
{ contact && contact.size ? <Contact data={ contact } /> : null }
|
||||||
{ license && license.size ? <License license={ license } /> : null }
|
{ license && license.size ? <License license={ license } /> : null }
|
||||||
{ externalDocsUrl ?
|
{ externalDocsUrl ?
|
||||||
<a target="_blank" href={externalDocsUrl}>{externalDocsDescription || externalDocsUrl}</a>
|
<a target="_blank" href={sanitizeUrl(externalDocsUrl)}>{externalDocsDescription || externalDocsUrl}</a>
|
||||||
: null }
|
: null }
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
|
import { sanitizeUrl } from "core/utils"
|
||||||
|
|
||||||
export default class OnlineValidatorBadge extends React.Component {
|
export default class OnlineValidatorBadge extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -32,6 +33,8 @@ export default class OnlineValidatorBadge extends React.Component {
|
|||||||
let { getConfigs } = this.props
|
let { getConfigs } = this.props
|
||||||
let { spec } = getConfigs()
|
let { spec } = getConfigs()
|
||||||
|
|
||||||
|
let sanitizedValidatorUrl = sanitizeUrl(this.state.validatorUrl)
|
||||||
|
|
||||||
if ( typeof spec === "object" && Object.keys(spec).length) return null
|
if ( typeof spec === "object" && Object.keys(spec).length) return null
|
||||||
|
|
||||||
if (!this.state.url || !this.state.validatorUrl || this.state.url.indexOf("localhost") >= 0
|
if (!this.state.url || !this.state.validatorUrl || this.state.url.indexOf("localhost") >= 0
|
||||||
@@ -40,8 +43,8 @@ export default class OnlineValidatorBadge extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (<span style={{ float: "right"}}>
|
return (<span style={{ float: "right"}}>
|
||||||
<a target="_blank" href={`${ this.state.validatorUrl }/debug?url=${ this.state.url }`}>
|
<a target="_blank" href={`${ sanitizedValidatorUrl }/debug?url=${ this.state.url }`}>
|
||||||
<ValidatorImage src={`${ this.state.validatorUrl }?url=${ this.state.url }`} alt="Online validator badge"/>
|
<ValidatorImage src={`${ sanitizedValidatorUrl }?url=${ this.state.url }`} alt="Online validator badge"/>
|
||||||
</a>
|
</a>
|
||||||
</span>)
|
</span>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { PureComponent } from "react"
|
|||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
import { getList } from "core/utils"
|
import { getList } from "core/utils"
|
||||||
import * as CustomPropTypes from "core/proptypes"
|
import * as CustomPropTypes from "core/proptypes"
|
||||||
|
import { sanitizeUrl } from "core/utils"
|
||||||
|
|
||||||
//import "less/opblock"
|
//import "less/opblock"
|
||||||
|
|
||||||
@@ -206,7 +207,7 @@ export default class Operation extends PureComponent {
|
|||||||
<span className="opblock-external-docs__description">
|
<span className="opblock-external-docs__description">
|
||||||
<Markdown source={ externalDocs.get("description") } />
|
<Markdown source={ externalDocs.get("description") } />
|
||||||
</span>
|
</span>
|
||||||
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a>
|
<a className="opblock-external-docs__link" href={ sanitizeUrl(externalDocs.get("url")) }>{ externalDocs.get("url") }</a>
|
||||||
</div>
|
</div>
|
||||||
</div> : null
|
</div> : null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
import { helpers } from "swagger-client"
|
import { helpers } from "swagger-client"
|
||||||
import { createDeepLinkPath } from "core/utils"
|
import { createDeepLinkPath, sanitizeUrl } from "core/utils"
|
||||||
const { opId } = helpers
|
const { opId } = helpers
|
||||||
|
|
||||||
export default class Operations extends React.Component {
|
export default class Operations extends React.Component {
|
||||||
@@ -101,7 +101,7 @@ export default class Operations extends React.Component {
|
|||||||
{ tagExternalDocsUrl ? ": " : null }
|
{ tagExternalDocsUrl ? ": " : null }
|
||||||
{ tagExternalDocsUrl ?
|
{ tagExternalDocsUrl ?
|
||||||
<a
|
<a
|
||||||
href={tagExternalDocsUrl}
|
href={sanitizeUrl(tagExternalDocsUrl)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
target={"_blank"}
|
target={"_blank"}
|
||||||
>{tagExternalDocsUrl}</a> : null
|
>{tagExternalDocsUrl}</a> : null
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Im from "immutable"
|
import Im from "immutable"
|
||||||
|
import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url"
|
||||||
import camelCase from "lodash/camelCase"
|
import camelCase from "lodash/camelCase"
|
||||||
import upperFirst from "lodash/upperFirst"
|
import upperFirst from "lodash/upperFirst"
|
||||||
import _memoize from "lodash/memoize"
|
import _memoize from "lodash/memoize"
|
||||||
@@ -722,6 +722,10 @@ export const shallowEqualKeys = (a,b, keys) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sanitizeUrl(url) {
|
||||||
|
return braintreeSanitizeUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
export function getAcceptControllingResponse(responses) {
|
export function getAcceptControllingResponse(responses) {
|
||||||
if(!Im.OrderedMap.isOrderedMap(responses)) {
|
if(!Im.OrderedMap.isOrderedMap(responses)) {
|
||||||
// wrong type!
|
// wrong type!
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import {
|
|||||||
fromJSOrdered,
|
fromJSOrdered,
|
||||||
getAcceptControllingResponse,
|
getAcceptControllingResponse,
|
||||||
createDeepLinkPath,
|
createDeepLinkPath,
|
||||||
escapeDeepLinkPath
|
escapeDeepLinkPath,
|
||||||
|
sanitizeUrl
|
||||||
} from "core/utils"
|
} from "core/utils"
|
||||||
import win from "core/window"
|
import win from "core/window"
|
||||||
|
|
||||||
@@ -885,4 +886,31 @@ describe("utils", function() {
|
|||||||
expect(result).toEqual("hello\\#world")
|
expect(result).toEqual("hello\\#world")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("sanitizeUrl", function() {
|
||||||
|
it("should sanitize a `javascript:` url", function() {
|
||||||
|
const res = sanitizeUrl("javascript:alert('bam!')")
|
||||||
|
|
||||||
|
expect(res).toEqual("about:blank")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should sanitize a `data:` url", function() {
|
||||||
|
const res = sanitizeUrl(`data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGV
|
||||||
|
sbG8iKTs8L3NjcmlwdD4=`)
|
||||||
|
|
||||||
|
expect(res).toEqual("about:blank")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not modify a `http:` url", function() {
|
||||||
|
const res = sanitizeUrl(`http://swagger.io/`)
|
||||||
|
|
||||||
|
expect(res).toEqual("http://swagger.io/")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not modify a `https:` url", function() {
|
||||||
|
const res = sanitizeUrl(`https://swagger.io/`)
|
||||||
|
|
||||||
|
expect(res).toEqual("https://swagger.io/")
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user