Skip to content

Commit 5f6ec8c

Browse files
authored
fix: mitigate "sequential @import chaining" vulnerability (#5616)
* `test/e2e-cypress/tests/features/xss/` -> `test/e2e-cypress/tests/security` * add tests * filter <style> tags out of Markdown fields * initialize OAuth inputs without applying `value` attribute
1 parent c8ad396 commit 5f6ec8c

File tree

11 files changed

+143
-7
lines changed

11 files changed

+143
-7
lines changed

src/core/components/auth/oauth2.jsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export default class Oauth2 extends React.Component {
9696
const AuthError = getComponent("authError")
9797
const JumpToPath = getComponent("JumpToPath", true)
9898
const Markdown = getComponent( "Markdown" )
99+
const InitializedInput = getComponent("InitializedInput")
99100

100101
const { isOAS3 } = specSelectors
101102

@@ -170,10 +171,10 @@ export default class Oauth2 extends React.Component {
170171
{
171172
isAuthorized ? <code> ****** </code>
172173
: <Col tablet={10} desktop={10}>
173-
<input id="client_id"
174+
<InitializedInput id="client_id"
174175
type="text"
175176
required={ flow === PASSWORD }
176-
value={ this.state.clientId }
177+
initialValue={ this.state.clientId }
177178
data-name="clientId"
178179
onChange={ this.onInputChange }/>
179180
</Col>
@@ -187,8 +188,8 @@ export default class Oauth2 extends React.Component {
187188
{
188189
isAuthorized ? <code> ****** </code>
189190
: <Col tablet={10} desktop={10}>
190-
<input id="client_secret"
191-
value={ this.state.clientSecret }
191+
<InitializedInput id="client_secret"
192+
initialValue={ this.state.clientSecret }
192193
type="text"
193194
data-name="clientSecret"
194195
onChange={ this.onInputChange }/>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// This component provides an interface that feels like an uncontrolled input
2+
// to consumers, while providing a `defaultValue` interface that initializes
3+
// the input's value using JavaScript value property APIs instead of React's
4+
// vanilla[0] implementation that uses HTML value attributes.
5+
//
6+
// This is useful in situations where we don't want to surface an input's value
7+
// into the HTML/CSS-exposed side of the DOM, for example to avoid sequential
8+
// input chaining attacks[1].
9+
//
10+
// [0]: https://github.com/facebook/react/blob/baff5cc2f69d30589a5dc65b089e47765437294b/fixtures/dom/src/components/fixtures/text-inputs/README.md
11+
// [1]: https://github.com/d0nutptr/sic
12+
13+
import React from "react"
14+
import PropTypes from "prop-types"
15+
16+
export default class InitializedInput extends React.Component {
17+
componentDidMount() {
18+
// Set the element's `value` property (*not* the `value` attribute)
19+
// once, on mount, if an `initialValue` is provided.
20+
if(this.props.initialValue) {
21+
this.inputRef.value = this.props.initialValue
22+
}
23+
}
24+
25+
render() {
26+
// Filter out `value` and `defaultValue`, since we have our own
27+
// `initialValue` interface that we provide.
28+
// eslint-disable-next-line no-unused-vars, react/prop-types
29+
const { value, defaultValue, ...otherProps } = this.props
30+
return <input {...otherProps} ref={c => this.inputRef = c} />
31+
}
32+
}
33+
34+
InitializedInput.propTypes = {
35+
initialValue: PropTypes.string
36+
}

src/core/components/providers/markdown.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export default Markdown
5151

5252
export function sanitizer(str) {
5353
return DomPurify.sanitize(str, {
54-
ADD_ATTR: ["target"]
54+
ADD_ATTR: ["target"],
55+
FORBID_TAGS: ["style"],
5556
})
5657
}

src/core/presets/base.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import Headers from "core/components/headers"
5353
import Errors from "core/components/errors"
5454
import ContentType from "core/components/content-type"
5555
import Overview from "core/components/overview"
56+
import InitializedInput from "core/components/initialized-input"
5657
import Info, {
5758
InfoUrl,
5859
InfoBasePath
@@ -105,6 +106,7 @@ export default function() {
105106
basicAuth: BasicAuth,
106107
clear: Clear,
107108
liveResponse: LiveResponse,
109+
InitializedInput,
108110
info: Info,
109111
InfoContainer,
110112
JumpToPath,

test/e2e-cypress/static/documents/petstore-expanded.openapi.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ info:
1313
url: https://www.apache.org/licenses/LICENSE-2.0.html
1414
servers:
1515
- url: http://petstore.swagger.io/api
16+
security:
17+
- Petstore: []
1618
paths:
1719
/pets:
1820
get:
@@ -152,4 +154,13 @@ components:
152154
type: integer
153155
format: int32
154156
message:
155-
type: string
157+
type: string
158+
securitySchemes:
159+
Petstore:
160+
type: oauth2
161+
flows:
162+
implicit:
163+
authorizationUrl: https://example.com/api/oauth/dialog
164+
scopes:
165+
write:pets: modify pets in your account
166+
read:pets: read your pets
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
* {
2+
color: red !important; /* for humans */
3+
}
4+
5+
h4 {
6+
display: none; /* for machines, used to trace whether this sheet is applied */
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
openapi: "3.0.0"
2+
3+
info:
4+
title: Sequential Import Chaining
5+
description: >
6+
<h4>This h4 would be hidden by the injected CSS</h4>
7+
8+
This document tests the ability of a `<style>` tag in a Markdown field to pull in a remote stylesheet using an `@import` directive.
9+
10+
<style>@import url(/documents/security/sequential-import-chaining/injection.css);</style>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
swagger: "2.0"
2+
3+
info:
4+
title: Sequential Import Chaining
5+
description: >
6+
<h4>This h4 would be hidden by the injected CSS</h4>
7+
8+
This document tests the ability of a `<style>` tag in a Markdown field to pull in a remote stylesheet using an `@import` directive.
9+
10+
<style>@import url(/documents/security/sequential-import-chaining/injection.css);</style>
File renamed without changes.

test/e2e-cypress/tests/features/xss/oauth2.js renamed to test/e2e-cypress/tests/security/oauth2.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
describe("XSS: OAuth2 authorizationUrl sanitization", () => {
22
it("should filter out a javascript URL", () => {
3-
cy.visit("/?url=/documents/xss/oauth2.yaml")
3+
cy.visit("/?url=/documents/security/xss-oauth2.yaml")
44
.window()
55
.then(win => {
66
let args = null

0 commit comments

Comments
 (0)