所謂、最近流行っているJavaScriptフレームワークでのXSSの例をいくつか挙げようと思う。
最近のJavaScriptフレームワークは賢いので、データをバインドする際にHTMLエスケープしてくれてXSSから保護してくれる。
しかしながら、保護が適用されないケースもあるため、過度にフレームワークに信頼しているとXSSを作り込んでしまう。
ここではReactとVue.jsでその例をいくつか挙げようと思う。
あくまで「やってしまうかもしれない例」というだけで、ReactやVue.jsの固有の問題というわけではないです。
React
javascript:
スキーム
aタグのhref属性にjavascript:
やdata:text/html
などの任意のスキームをユーザーが設定できる場合、XSSが生じる。
これはReactに限った話ではなく、古くからある手法だが、未だにたまに見る。
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div className="App">
<a href={this.props.link}>{this.props.link}</a>
</div>
);
}
}
ReactDOM.render(<App link="javascript:alert(1)" />, document.getElementById('root'));
対策としてはスキームをhttp://
かhttps://
のみに制限するなどする。
ユーザーが任意のpropsを注入できる場合
https://hackerone.com/reports/49652 にあるように、ユーザーの入力値をそのままReact.createElement
のpropsとして渡した場合などにはXSSが生じる。
import React from 'react';
import ReactDOM from 'react-dom';
// const props = JSON.parse('{"name": "foo"}');
const props = JSON.parse('{"name": {"dangerouslySetInnerHTML" : { "__html": "<svg/onload=alert(1)>"}}}');
ReactDOM.render(React.createElement("p", props.name), document.getElementById('root'))
dangerouslySetInnerHTML
はい。
https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div className="App">
<div dangerouslySetInnerHTML={{__html: '<svg/onload=alert(1)>'}} />
</div>
);
}
}
export default App;
rawHtml
みたいな名前よりdangerouslySetInnerHTML
みたいに危険であることが誰にでも分かるような名前って良いよなーと思います。
Server Side Rendering
React + ReduxでSSRするときにこんなことをすると思う。
https://redux.js.org/recipes/server-rendering#inject-initial-component-html-and-state
const renderFullPage = (html, initialState) => {
return `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>React Router Redux Express</title>
</head>
<body>
<div id="reactbody">
${html}
</div>
<script>
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}
</script>
<script src="/js/app.bundle.js"></script>
</body>
</html>
`
};
export default renderFullPage;
脆弱なのは window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}
の部分で、initialState
にユーザー入力値が含まれて以下のようになる場合、XSSが生じる。
initialState = {
name: '"></script><script>alert(1)</script>'
}
Redux公式では ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')
のような形で <
を置換する形で対応している。
https://redux.js.org/recipes/server-rendering#security-considerations
VueJS
javascript:
スキーム
javascript:alert(1)
のやつ。
<a v-bind:href="link">link</a>
v-html
はい。
https://jp.vuejs.org/v2/guide/syntax.html#%E7%94%9F%E3%81%AE-HTML
// Safe
<p>Using mustaches: {{ rawHtml }}</p>
// XSS
<p><span v-html="rawHtml"></span></p>
compile
https://vuejs.org/v2/api/#Vue-compile
compile
に信用できない値を入れてはいけない。
まぁ、普通こんなことはしないと思うのだけれど…
let res = Vue.compile('<div><img src="x" onerror="alert(1)"></div>')
ユーザーが任意のpropsを注入できる場合
ユーザーが任意のpropsを注入できて、かつ、それをcreateElementに直接渡している場合。
<div id="app">
<anchored-heading>Hello world!</anchored-heading>
</div>
<script>
window.addEventListener('load', function () {
const props = JSON.parse('{"domProps": { "innerHTML": "<img src=\'x\' onerror=\'alert(1)\'>" }}')
let anchoredHeading = Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h1', props, 'hoge'
)
},
})
new Vue({
el: '#app',
components: {
'anchored-heading': anchoredHeading
}
})
});
</script>
Server Side Rendering
RailsとかPHPでちゃんとエスケープされてる…と思ってるとハマる。
http://kamihikouki.hatenablog.com/entry/2017/09/09/175815
ちょっと考えると当たり前ではあるんだけど、コンテキストが異なるのでやってしまう人はいるんじゃないだろうか。
<form action="">
<label>
<input
type="text" name="v" value=""
/>
<button>Submit</button>
</label>
</form>
<div id="app">
<div>
<?= htmlspecialchars($_GET['v'], ENT_QUOTES, 'utf-8') ?>
</div>
</div>
<script>
window.addEventListener('load', function () {
new Vue({
el: '#app',
});
});
</script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.js"></script>
このとき$_GET[v]
に{{ constructor.constructor("alert(1)")() }}
など渡すとアラートが出る。
手っ取り早く対策するにはv-pre
ディレクティブを使えば良い。
<div id="app">
<div v-pre>
<?= htmlspecialchars($_GET['v'], ENT_QUOTES, 'utf-8') ?>
</div>
</div>
まとめ
他にも共通なものとしてeval
とかあるけど、自明なので省略。
「フレームワークがやってくれてるでしょ」と思って書いてるとうっかり作り込んでしまっていることもあるので、気をつけていきましょう。