日々の健全なXSSにお役立てください。
●極力短いコードでXSS
本題に入る前に軽く整理します。オーソドックスなXSSであれば、おなじみの次のようなコードになるでしょう。
"><script>alert(document.cookie)</script> *1
しかしながら、Reflectedするのにもかかわらず、場合によっては文字数制限という問題にぶち当たることもあるかと思われます。その場合、より短いコードでXSSを成功させる必要性があります。どこにReflectedされるかによって書き方は変わりますが、大まかには下記のどれかになるでしょう。
◆HTML Body(HTML構文内)
BAD: <input type="text" value="<?php echo $GET['name']; ?>">
PoC: "><script>alert(1)</script> (28文字)
PoC: "onmouseover=alert(1)+ (23文字) ※+は半角スペースとして処理されます
◆HTTP Body(JavaScript構文内)
BAD: setName('id', '<?php echo $GET['key']; ?>');
PoC: ');alert(1)// (14文字)
◆HTTP Header(実質HTML構文)
BAD: <?php header("Set-Cookie: {$GET['cookie']}"); ?>
PoC: %0a%0a<script>alert(1)</script> (32文字)
%0a%0a<html><body><script>alert(1)</script></body></html><!--+*2
◆Cookieなどの変わり種系
本稿では言及しません。
余談ですが、*3
■本題
本稿では、HTML構文内でのXSSを取り上げます。短いコードを生み出すには、削れるところは削る他ないかと思われます。削れそうなところ、ここでは「イベントハンドラ」を削ります。解へたどり着くためには、JavaScriptのイベントハンドラ一覧をまじまじと眺めましょう。
制約事項として、IE, Firefox, Chrome, Safariの最新バージョンで動作することとします。*4 また、PCとモバイル端末とのタッチ操作の有無などのギャップは考慮しません。結論を書くと次のコードに落ち着きました。
<i/oncut=alert(0)> (19文字)
わずか5文字の oncut を使います。全体として基本形の28文字からだいぶ削れました。oncutはクリップボードに切り取り(Ctrl+X)をした際に発火するイベントです。
ただし、テキストボックスやテキストエリアなどの文字入力フォームが無いと発火しないので使い所を選びますが……oncopyより1文字少ない!!!え?違うそうじゃないって??
●CSSを駆使して即時発火させる
onmouseoverなどのイベントハンドラによる発火が必要なXSSの場合、「画面上に表示されている、戻るリンク上にマウスのカーソルを移動させます。」といった操作が必要となります。攻撃者側視点としても、悪意あるJavaScriptコードは、システム利用者の特別な操作を必要とせずに、ページが表示された瞬間実行させる方が都合がいいでしょう。
次のCSSをJavaScriptコードと一緒に追記することによって、画面上のどこでもマウスを動かすと発火させることが可能です。
style="position:absolute;width:100%;height:100%;top:0;left:0;"
絶対位置(position:absolute;)で左上(top:0;left:0;)から画面全体(width:100%;height:100%;)に適用させるCSSです。ハイパーリンクの場合、動作的には画面全体がリンクになります。(画面上、どの部分をクリックしてもページ遷移します。) XSSと組み合わせると次のようになります。
<a href="localhost/?page="onmouseover="alert(1)"style="position:absolute;width:100%;height:100%;top:0;left:0;">Back</a>
このCSSによりマウスを動かした瞬間発火するため、即時発火すると言っても過言ではないでしょう。
本来リンクのBackは見出しの下に表示されるはずですが、左上に表示され、マウスを動かしただけでJavaScriptが動作します。onmouseover以外でも発火するかと思われます。
●記号を使わないXSS
意図的にXSSさせる書き方以外、基本的には次のいづれかの記号「'"><;()/」が必要になりますが、微妙な刺さり方をしている場合、XSSが起こりそうだけど文字数制限やWAFによって弾かれるというケースもあるかと思います。タイトルに偽りありですが、「.」のみ使えればXSSは出来ます。
仕様なのでなんとも言えませんが、window.nameプロパティにJavaScriptコードを埋め込んでも評価されます。window.nameはウィンドウ名をセットするプロパティですが、外部から操作可能なプロパティです。この手法で攻撃すると下記のようになります。
意図したJavaScriptコードが実行されました。擬似的に再現するために、2つの疑似攻撃用ページを使っています。
・window2.html
<form action="./window1.html?url=window.name" name="auto" method="post" target="javascript:alert(1)">
攻撃対象へ自動的にPOSTするHTMLページです。(ポップアップブロックの例外が必要です) a要素のtarget属性の値にJavaScriptコードを仕込んでます。これで window.name に値がセットされます。ポイントは2箇所です。Reflectedされる微妙な箇所(QueryStringのurlの値)と、window.nameにセットさせるJavaScriptコード(target属性の値)です。target属性の値はブラウザ側で保持され、サーバ側には送信されません。
・window1.html
location.hrefでwindow.nameの中身にリダイレクトされるので、javascript:alert(1) が実行されます。
location.hrefでリダイレクトされるという決め打ちのケースですが、概念的にはこのような流れになります。巷ではインラインフレームを使う方法もありますね。location.href以外、例えばeval(window.name)でも同じ結果になります。刺さり方によりますが、alert(), confirm()や特定の記号が使えない場合に上手いこと刺さるかもしれないですね。
●WAFをバイパスしそうなXSS
WAFの設定に大きく依存しますが、経験上AWSとmod_securityをバイパスしました。
・Unicode Escape Sequence の文字列を変数にセットして評価させる
i='\u0061\u006c\u0065\u0072\u0074\u0028\u0030\u0029';new Function(i)();
Unicode Escape Sequence でエンコードすることによって、alert(0)という文字列を使うことなくコードが実行出来ます。
・onhashchange を使う
<input type="text" value=""><body/onhashchange=alert(1)><a href=#>xss</a>">
イベントハンドラ onhashchange を使ったXSSです。onhashchangeはwindowのハッシュ値が変わった時に発火します。上記の場合、画面上の「xss」のアンカーリンクをクリックすると発火します。onhashchange は、あまり聞かないイベントハンドラですので、WAFのブラックリストに設定されていないケースがあります。
このほか、色んな手法が山ほどあるので、ググってプロのTipsを見たほうが良いかも。
●hiddenでXSS
悪魔の証明のようなものですが、不可視フォームの <input type="hidden"> に刺さってしまってもどうにかしてalert(1)を出したいところ。基本的にどのイベントハンドラも発火しない仕様です。が、一部の環境において、発火する組み合わせがあります。
<input type="hidden" name="id" value=""onformchange="alert(1)">
古いバージョンのOperaと、onformchange の組み合わせで発火します。onformchange は、フォームの内容が変更された上で入力から外れた時に発火します。
Opera 12.16 でXSSを確認しました。*5 少し調べてみるとバグのようです。
hidden フィールドでXSSさせる手法はこのほかにもあります。
参考情報:Index of /pub/opera/ ※古いバージョンのOperaがひっそり配布されています。(公式)
●引用符で文字列を囲まないalert
alert(xss) ではダイアログは出ず、引用符で囲い alert("xss") としなければなりませんが、alert(/文字列/.source) という書き方でも問題ありません。XSS現場で使えるかどうかは微妙ですが。
<input type="text" value=""><script>alert(/クォート いらない よー/.source)</script>">
半角スペースが入ったり、2バイト文字でも大丈夫です。
これらがベストな手法ということではありません。よりスマートな攻撃手法も存在しています。より良い手法や、内容に誤りがありましたら、コメントを残していただけると幸いです。
世の中がセキュアなWebアプリケーションでありますように。
*1:セッションIDなどのCookie情報にアクセスできること、つまりXSSの脆弱性によって窃取可能であるリスクが内在していることを示すために document.cookie を採用しています。なお、httponly属性が付与されているなどの場合は、location.href を代用することがあります。
*2:文法に従うと左記のようになりますが、HTMLは良きに計らってくれます。
*3:古いIEのバージョンで、キャッシュされたコンテンツのContent-Typeを引き継ぐ謎の仕様を発見をしました。つまり、text/htmlな適当なコンテンツをキャッシュさせておけば、ページの手動更新をした場合、text/plainなどでも“コンテンツの内容が変わっている”のにも関わらずtext/htmlで処理されてしまうためXSSが成功するというもの。1年以上前の記憶なのであんまり覚えてないし、既知かもしれないし、攻撃難度は高いと思われるので参考情報程度に。(Content sniffing ではないっぽい)※これではないけど、検証済みの類似したやつは某弊社で某IPAに報告しといたよ。
*4:限定的な環境でのXSSはリスクアセスメントの際に、基準から慎重に再考することがあります。勿論、原則は主観的な判断はしません。例:IEの画像によるXSSなど
*5:バージョン12.16はmacOS, LinuxでのPrestoエンジンの最終バージョンです。Windowsの場合、CipherをUpdateさせたバージョン16.18が最終バージョンと記憶しています。