XSS攻撃とは?攻撃されないための対策を紹介
XSSについて
XSS(クロスサイトスクリプティング)は、Webアプリケーションの脆弱性の一つで、不正なJavaScriptコードを注入することにより、攻撃者がユーザーのブラウザで実行されるようにするものです。
Webアプリケーションの脆弱性の届出が一番多いものがクロスサイトスクリプティングとも言われています。
不正なJavaScriptコードを注入するというのはどういう場合に起こり得るのでしょうか?
それはユーザーが入力した文字列をそのままHTMLに挿入してしまうことによって発生します。
具体的にクロスサイトスクリプティングが実行されてしまう例を見てみましょう
XSSが実行されてしまう状況
例えば、以下のようなHTMLがあるとします。
<div>
<p>検索TARGET: [ユーザーが入力した文字列]</p>
</div>
上記の [ユーザーが入力した文字列] の箇所にはクエリストリングのtargetのキーの値が出力されるとします。
そのためクエリストリングとして”炊飯器”と入力した場合は以下のように出力されるのが本来の使い道です。
https://example.com?target=”炊飯器”
<div>
<p>検索TARGET: 炊飯器</p>
</div>
しかし悪意のあるユーザーがJavaScriptコードを入力することも可能です。
試しに以下のように入力した場合は、ユーザーのブラウザ上でスクリプトが実行されてしまいます。
https://example.com?target=”<script>alert('XSS')</script>”
<div>
<p>ユーザーが入力したテキスト: <script>alert('XSS')</script></p>
</div>
スクリプトによって強制的に悪意のあるウェブサイトにリダイレクトさせたり、機密情報を盗み見られたり、アプリケーションコードを改ざんしたり、ユーザーの意図しない動作を実行させたりなどすることなどができてしまいますね。
XSSにはいくつか種類があるので対策方法が異なります。
XSSの種類と特徴についてみていきましょう。
XSSの分類
反射型XSS
反射型XSS攻撃は、攻撃者がリンク、フォーム、その他の入力メカニズムを介して、不正なスクリプトを含むURLをユーザーに送信することによって発生します。ユーザーがそのリンクをクリックすると、スクリプトが実行されます。
例えば、以下のようなURLがあるとします。
https://example.com/search?query=<script>alert('XSS')</script>
このURLをクリックすると、ユーザーのブラウザ上でスクリプトが実行されます。
※実際のサイトでは対策されています
蓄積型XSS
蓄積型XSS攻撃は、攻撃者が不正なスクリプトを含むデータをデータベースに保存することによって発生します。その後、ユーザーがそのデータにアクセスすると、スクリプトが実行されます。
例えば、コメント欄に以下のようなスクリプトが投稿された場合を考えてみましょう。
<script>alert('XSS')</script>
コメント欄はデータベースに保存され、他のユーザーがそのコメントを閲覧する際にスクリプトが実行されます。
DOM-Based-XSS
DOM-Based-XSS攻撃は、攻撃者がJavaScriptのDOM(Document Object Model)を操作することによって発生します。攻撃者は、ウェブページのDOMを変更することによって、不正なスクリプトをユーザーのブラウザで実行させます。
例えば、以下のようなスクリプトがあるとします。
<script>
var name = window.location.hash.substr(1);
document.write("こんにちは、" + name + "さん!");
</script>
このスクリプトは、URLのハッシュ部分に名前が含まれている場合に、その名前を表示するものです。
https://example.com/#Alice
このURLをクリックすると、ユーザーのブラウザ上でスクリプトが実行され、こんにちは、Aliceさん!
というメッセージが表示されます。
攻撃者は、以下のようにURLを変更することによって、スクリプトを注入することができます。
https://example.com/#<script>alert('XSS')</script>
このURLをクリックすると、ユーザーのブラウザ上でスクリプトが実行され、alert('XSS')
というポップアップが表示されます。
DOM-Based-XSSに関してはフロントエンドで対策をすることができる範囲です。
次にクロスサイトスクリプティングの対策方法についてみていきましょう
XSSをどう対策するか
XSSの対策方法としていくつか考えられますが、この記事では以下の方法を紹介します。
- 入力データのエスケープ
- 属性値の文字列をクォーテーションで囲む
- CookieにHTTPOnlyフラグの設定
- ライブラリを利用する
- フレームワークを利用する
- Sanitizer APIを利用する
それぞれ具体例を用いて解説していきます。
1. 入力データのエスケープ
入力データのエスケープは、入力されたデータに含まれるHTML特殊文字(<、>、&、"、')をエスケープすることによって、不正なHTML要素や属性を生成することを防ぎます。
具体的には、以下のようにすることができます。
<script>
function escapeHTML(str) {
return str.replace(/[&'`"<>]/g, function(match) {
return {
'&': '&',
'\\'': ''',
'`': '`',
'"': '"',
'<': '<',
'>': '>'
}[match]
});
}
</script>
このように、入力されたデータをエスケープすることで、不正なHTML要素や属性を生成することを防ぐことができます。
ただし、この方法はエスケープ漏れがある場合に対策が効かなくなるため、正しくエスケープすることが重要です。そのために後述するライブラリやフレームワークの利用を推奨します。
2. 属性値の文字列をクォーテーションで囲む
<div class="example">
<img src="image.jpg" alt="Example Image">
</div>
上記の例では、class属性にexampleという値が設定されています。この属性値をクォーテーションで囲まない場合、以下のような攻撃が可能になってしまいます。
<div class=example onmouseover="alert('XSS')">
<img src="image.jpg" alt="Example Image">
</div>
class属性の値がexampleである要素にマウスオーバーすると、alert('XSS')というポップアップが表示されます。これは悪意あるJavaScriptコードを実行するものであり、攻撃者にとって都合のいいようにWebページを改ざんすることができます。
このような攻撃を防ぐためには、属性値の文字列をクォーテーションで囲べば良いです
<div class="example" onmouseover="alert('XSS')">
<img src="image.jpg" alt="Example Image">
</div>
このように、class属性の値をクォーテーションで囲むことで、攻撃者による悪意あるJavaScriptコードの実行を防止することができます。
3. CookieにHttpOnlyフラグの設定
Cookieに機密情報、ユーザー独自の情報などが記載されているため、Cookieの値に対して攻撃者は働きかけてきます。
CookieをJavaScript経由で取得することができないようにするには簡単で、CookieにHttpOnlyという属性をつけたら良いのです。
JavaScriptでCookieの情報を扱わなければ良い場合は基本的にHttpOnlyを付与するようにしましょう。
4. ライブラリを利用する
代表的なものとして、DOMPurifyが挙げられます。
DOMPurifyは、HTML、SVG、MathMLなどのテキストデータを安全にレンダリングするためのライブラリです。DOMPurifyは、入力されたテキストデータを解析し、不正なHTML要素や属性、JavaScriptコードを削除することで、XSS攻撃を防止します。
具体的には、以下のようなコードでDOMPurifyを使用することができます。
<script src="<https://cdn.jsdelivr.net/npm/dompurify@2.2.7/dist/purify.min.js>"></script>
<script>
var dirty = '<script>alert("XSS")</script>';
var clean = DOMPurify.sanitize(dirty);
console.log(clean);
</script>
上記のコードでは、dirty
変数に不正なJavaScriptコードを代入し、DOMPurify.sanitize()
メソッドを使用して、不正なコードを削除したクリーンなテキストを取得しています。
5. フレームワークを利用する
Reactでは、XSSの脆弱性を防ぐために、JSX内でのHTMLエンコードが自動的に行われます。JSXでは、HTMLタグを表現するために<
と>
を使用しますが、これらは自動的にHTMLエンコードされます。このため、ユーザーが安全でない入力を提供しても、その入力はHTMLとして解釈されることはありません。
また、Reactでは、JavaScriptコードがHTMLとして解釈されるのを防ぐために、dangerouslySetInnerHTML
プロパティを使用してHTMLをレンダリングすることを推奨しません。代わりに、React要素を使用してコンポーネントをレンダリングし、必要に応じてpropsを渡すことが推奨されます。
Sanitizer APIを利用する
Sanitizer APIは、HTML、CSS、JavaScript、URLなどの入力データを安全に処理するためのAPIです。ブラウザで標準実装されているため別途ライブラリなどを読み込む必要がありません。良いですね。
以下は、Sanitizer APIを使用して入力データを安全に処理する例です。
const element = document.getElementById("div");
const unsafe = decodeURIComponent(location.hash.slice(1));
const sanitizer = new Sanitizer();
element.setHTML(unsafe, sanitizer);
Discussion