はじめに
今回はPortSwiggerが提供するLabを利用して、HTML属性内のXSS脆弱性の具体的な解説と、どうしてその様な脆弱性が生み出されてしまうのかを実際のコードに着目して理解を目指します。
-
取り組むLab: Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped
-
ゴール: コメント投稿者の名前をクリックした時に alert() を実行させる。
-
前提: サーバー側で「HTMLエンコード」が行われている状況。
2. 基礎知識:文字実体参照とは
HTMLにおいて < や " など、何らかの意味を持つ特殊文字を扱うための仕組みです。
多くは紹介しませんが、例えば < → <、> → >、シングルクォート ' → ' 、ダブルクォート " → " などの変換がそうです。
簡単にまとめると 「ブラウザが表示する時に人間に読める文字に戻してくれる機能」であり、特殊文字をただの文字として扱う事が出来ます。
3. 偵察:エスケープ処理

とりあえず普通に ' や <script> を入れてみます。

サーバー側で特殊文字は処理されているのでスクリプトが実行できないことが分かりますね。 タグの中でスクリプトを動作させるのは諦めて、ここからはaタグの属性値を狙います。確認してみると、入力値が反射している属性値はhrefとonclickです。 まずはhref属性値について色々やってみましょう。
WebUIからの入力はhttpやhttpsから始まる文字列を強制されているので、一例としてBurp Suiteで書き換えてから送信してみましょう。
そのままURLに反射されることを期待して
javascript:alert(1)
を入力してみましたが、レスポンスは”Invalid website.” で残念でした。
次はonclick属性値を試してみましょう。
4. 仮説:属性の中ならどうなる?
ここからはoneclick属性値を使ってjavascriptの実行を目指していく中で、一つ仮説を立てていきます。
- 仮説: サーバーは
'を検知して\'に変えている。だが、あらかじめHTMLエンコードされた'を入力には何も処理を行わない。
この仮説が正しいとするなら、以下の入力値はHTMLパーサがデコードする際に ' に戻されるはずです。その結果onclick属性値の値として入力値を計算する過程でalertが実行されてしまいます。
- 入力: “
'-alert(1)-'”
この仮説の元に検証した結果が以下の通りです。

コメントに残されたaタグをクリックするとalertが実行されてしまいました。Stored XSSが成功しています。
5. 原理:なぜ攻撃は成功したのか?
この脆弱性はJSエンジンに渡る前にHTMLパーサがデコードしてしまったことが原因です。
ブラウザの処理は、まずHTMLパーサがonclick 属性の中にある ' を見つけて、' にデコードします。
デコードする際には基本的に問題はありませんが、完成されたテキストを改めてjavascriptが解釈する場合はスクリプトであると解釈されてしまいます。
今回だとonclick属性値をjavascriptとして解釈する瞬間には、'が'にデコードされた状態で読み込まれるため、JSエンジンは素直に実行してしまいます。
これが今回の脆弱性の概要です。
6. まとめ:HTMLエンコードしておけば安全か?
HTMLエンコードは多くの場面でXSS対策として有効な手段です。ただ今回の様に、ユーザの入力値が反射する部分をしっかり考慮していないと、意図せぬ脆弱性が生まれてしまう可能性があります。 「HTMLエンコードしておけば安全」という考え方を捨て、コンテキストを考慮して対策を組み立てることが大切です。