前回はクロスサイトスクリプティング(XSS)の概要についてまとめた。
今回はなぜ脆弱性が生まれるのかという原因とその対策方法について。
なぜXSS脆弱性が生まれるのか
前回見たように、XSS脆弱性が生じる原因は、一言で言うとHTML生成の際にHTMLの文法上特別な意味を持つ特殊記号(メタ文字)を正しく扱っていないからである。
それによって開発者の意図しない形でHTMLやJavaScriptを埋め込まれてしまうことにより生じる。
つまり、攻撃者がHTMLやJavaScriptを生成するために使う「”」や「<」という文字を特殊記号として扱うのではなく、ただの文字列として扱う必要がある。
この処理をエスケープという。
前回のおさらいもかねて、例を挙げる。
以下のようなinputタグを考える。
<input type=text name=email value="<?php echo $_GET['p']; ?>">
ここで攻撃者がpに以下のような値を与えるとする。
"+onmouseover%3d"alert(document.cookie)
%3dはエンコードされていて、実際には「=」になる。
そうすると以下のように展開される。
<input type=text name=email value="" onmouseover="alert(document.cookie)">
与えた最初の「”」によって元々あったvalueが終わってその後のonmouseoverが追加されてしまった。
つまり対策としてはこの「”」をエスケープしてただの文字列として扱う必要がある。
基本的な対策
これまで見てきたように原因は「<」や「”」のエスケープ漏れなので、これらをエスケープすることが基本的な対策として重要である。
じゃあどうやってエスケープするのかと言うと、それぞれの言語で関数があるっぽい。
PHPだとhtmlspecialcharsとかいう関数。
あんまよくわかってないけど、言語やフレームワークごとに適切な方法があってそれを使えってことなのかな。
なんか対策とか言っておきながらめちゃくちゃ投げやりな感じになってしまった。笑
ちなみにReactは基本的にフレームワーク としてエスケープ処理をしてくれるようになっている。
有能 of 有能。
ただし、inputタグはエスケープしてくれるものの、aタグのhref属性にはエスケープ処理がされないため、aタグにユーザーの入力値(stateやprops)を直接埋め込むのは避けたほうがいいっぽい。
https://zenn.dev/yuuhu04/books/xss-anti-pattern-of-react-and-vue/viewer/xss-over-react
URLにユーザーの入力値を入れる場合の対策は後述する。
保険的な対策
XSSは対策が必要な箇所が多く、漏れが発生しやすい。
一番の対策は特殊文字のエスケープ処理なんだけど、それ以外にも保険的な対策を実施しておくことで被害を低減できる場合がある。
・入力値検証
・CookieのHttpOnly属性
・X-XSS-Protectionレスポンスヘッダの使用
入力値検証
たとえば数字を入力させる部分では数値の入力のみ受け付けるようにしておけば、スクリプトの入力を防げる。
ただし、自由書式の入力欄では対策できない。
CookieのHttpOnly属性
これがtrueのcookieはJavaScriptによる読み出しをできないようにするもの。
これによってJavaScriptによるセッションIDの盗み出しを防止できる。
X-XSS-Protectionレスポンスヘッダ
ブラウザにはXSSフィルタというセキュリティ機能が実装されているものがあるらしい。
これがあればXSSをブラウザが検知して無害な出力に変更してくれるらしい。便利。
デフォルトで有効。
発展的な対策
文字列のエスケープ処理を行えばそれで十分かというとそうでもない。
以下の2つに対しては別途対策を取る必要がある。
・URLを属性値とするもの
・JavaScriptの一部を動的に生成する場合
URLを属性値とするもの
inputタグに対しては有効だが、aタグのhref属性やimgタグ、iframeのsrc属性などはURLを属性値としている。
このURL属性値の部分をユーザー入力したものを当てはめる場合、ユーザーが以下のような形式を入力するとJavaScriptが起動してしまう。
<a href="javascript: alert(document.cookie)">hoge</a>
これを防ぐための対策は以下の通り。
・http:またはhttps:で始まる絶対URLのみを許可する
・/で始まる相対URLのみを許可する
・URLのドメインが意図しないものだった場合はエラーにする
上2つは必ずURLであることが保証されている状態じゃないとダメにするってこと。
最後のは入力されたURLが許可したドメインじゃない場合は全部弾くというもの。
リンク先のドメイン名が決まっている場合はこれをしたほうが安全。
JavaScriptの一部を動的生成する場合
JavaScriptの中の要素を動的に変更する場合にも色々とエスケープ処理をする必要がある。
ただ、JavaScriptのエスケープルールは複雑なため、できる限りJavaScriptを動的生成することは避けたほうがいいらしい。
それでも動的生成したいという場合は以下の2種類のどちらかの方法を取る。
- カスタムデータ属性を使ってJavaScriptから参照する
- インラインJSONPを使ってエスケープする
この辺のまとめは省略する(疲れた笑)。
まとめ
基本的にはエスケープ処理をしっかりした上で、入力値の検証やCookieはHttpOnlyにするのが良さそうかな。
必要な対策が多いのでフレームワーク 側でエスケープ処理してくれたりするのは便利だなーと思った。
React使う時は入力値検証とaタグのURL属性は気をつける。
コメント