本サイトは、快適にご利用いただくためにクッキー(Cookie)を使用しております。
Cookieの使用に同意いただける場合は「同意する」ボタンを押してください。
なお本サイトのCookie使用については、「個人情報保護方針」をご覧ください。

最新情報

2016.11.04

機械学習でWebアプリの脆弱性を見つける - Reflected XSS 編 -

1. はじめに


 今年3月、Black Hat ASIA Arsenalに診断AI「SAIVS」を出展した際、訪問者から「機械学習を使って自動でWebアプリの脆弱性は検出できないか?」という要望を複数受けました。今回はこのご要望にお応えすべく、SAIVSに「機械学習でWebアプリの脆弱性を見つける」能力を追加しましたので、そのアイデアとデモを紹介したいと思います。

 今回のアイデアは下記の実現を目標にしています。


少ない手数で脆弱性を検出する。」


 多数のシグネチャ(脆弱性の検査パターン)を総当たりで試行するのではなく、検査対象のWebアプリの挙動を見ながら少ない試行(理想は1回)で脆弱性を検出することを目指しています。

 なお、Webアプリの脆弱性と言っても多種多様ですが、今回は「Reflected Cross Site Scripting(以下、RXSS)」を対象にします。また、RXSSも単純なものから複雑なものまで様々ですが、現時点では比較的単純なパターンを対象にしています。今後、徐々に複雑なパターンにも対応していく予定です。


 あと、本ブログでは機械学習アルゴリズムにも触れますが、これらの詳細な説明は行いません。機械学習アルゴリズムの詳細については、「6.参考文献」に挙げた文献でご確認いただければ幸いです。


 本ブログは、2章で「RXSSの動作原理」を説明した上で、3章で「アイデアおよび実現方法」を説明します。そして、4章ではSAIVSのデモをお見せします。デモは脆弱性スキャナ評価用の「Webseclab」と、脆弱性診断員の練習に使用される「Google gruyere」に対して検査を行います。最後に5章で「今後の展望」を述べて締めくくります。



2. RXSSとは?


 メジャーな脆弱性なので説明は不要だと思いますが、念のため。

 Wikipediaでは以下のように説明されています。


「ウェブページの部分をユーザからの入力をそのままエコーバック(オウム返し)することによって生成しているアプリケーションのセキュリティ上の不備を利用して、サイト間を横断して悪意のあるスクリプトを注入する攻撃のことをいう。」


 より理解するために、実際にRXSSの検査を行った際のログを使って説明します。


 例えば、超脆弱なWebアプリ「http://xxx/case3/?input=testData」にアクセスすると、Figure 1のレスポンスが返されたとします。


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Case 3 - RXSS</title>
</head>
<body>
<input type="text" value="testData">
</body>
</html>
Figure 1 超脆弱なWebアプリのレスポンス

 パラメータ「input」の値「testData」が、INPUTタグのVALUE属性値にエコーバックされていることが分かります。


 今度はパラメータ「input」に以下の値を入れてみます。


http://xxx/case3/?input="/><script>alert('XSS');</script>
Figure 2 パラメータにスクリプトを入れた様子

 すると、Figure 3のレスポンスが返されます。


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Case 3 - RXSS</title>
</head>
<body>
<input type="text" value=""/><script>alert('XSS');</script>">
</body>
</html>
Figure 3 スクリプトが挿入された様子

入力した「"/>」によってVALUE属性値とINPUTタグが閉じられ、後続のSCRIPTタグ「<script>alert('XSS');</script>」が挿入されていることが分かります。

 これにより、「alert('XSS')」を実行することができます。


 このように、アプリ側が想定していない値を入力することで、任意のスクリプトをブラウザ上で実行させることができます。また、スクリプトの代わりにHTMLタグを入力した場合は、ブラウザ上に表示される画面を改ざんすることができます。

※RXSSを悪用する場合は、悪意のある者が用意した罠ページ(Figure 2のパラメータ値を使って強制的に超脆弱なWebアプリにアクセスさせる)に被害者を誘導させることで、ユーザのブラウザ上で悪意のある者が用意したスクリプトの実行や画面の改ざんを行います。


 以上から、RXSSを検出するためには、HTML構文の理解が必須になります。また、入力値はJavaScript内にエコーバックされることもありますので、JavaScript構文の理解も必須になります。


 次に、ちょっとセキュアなWebアプリを考えてみます。

 先ほどと同じノリでスクリプトを入力してみます(Figure 4)。


http://xxx/case4/?input="/><script>alert('XSS');</script>
Figure 4 パラメータにスクリプトを入れた様子

 すると、Figure 5のレスポンスが返されます。


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Case 4 - RXSS</title>
</head>
<body>
<input type="text" value=""/> alert('XSS');">
</body>
</html>
Figure 5 ちょっとセキュアなWebアプリのレスポンス

 入力したSCRIPTタグ「<script></script>」がWebアプリ側で排除されており、スクリプトを実行することができなくなっています。

 このように、ちょっとセキュアなWebアプリでは、RXSS対策としてあからさまなスクリプト挿入を防ぐためにSCRIPTタグのサニタイズを行っていることが分かります。


 このサニタイズを回避してスクリプトを実行させるには、SCRIPTタグを使わずにスクリプト実行が可能な値を入力する必要があります。

 そこで、今度は以下の値を入力してみます(Figure 6)。


http://xxx/case4/?input="onmouseout=alert('XSS')"
Figure 6 サニタイズ回避のスクリプトを入れた様子

 すると、Figure 7のレスポンスが返されます。


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Case 4 - RXSS</title>
</head>
<body>
<input type="text" value=""onmouseout=alert('XSS')"">
</body>
</html>
Figure 7 スクリプトが挿入された様子

入力した「"」によってVALUE属性値が閉じられ、後続のイベントハンドラ(onmouseout)が挿入されていることが分かります。これにより、ブラウザ上に表示された入力フォームからマウスポインタが外れたタイミングで「alert('XSS')」が実行されます。

 このように、サニタイズしていても、サニタイズを回避するような値を入力することで、スクリプトを実行させることができます(=RXSSを検出できる)。


 以上のことから、RXSSを検出するためには、サニタイズの回避も必須になります。


 纏めると、RXSSの検出には最低限以下の「3要件」を満たす必要があります。


  • HTML構文の理解
  • JavaScript構文の理解
  • サニタイズの回避

 さて、これらをどうやって機械学習で実現するのでしょうか?

 次章で考案したアイデアを説明します。



3. どうやって実現するか?


 今回は以下の機械学習アルゴリズムを使って実現します。


No 要件 使用する機械学習アルゴリズム
1 HTML構文の理解 LSTM(Long Short Term Memory)
2 JavaScript構文の理解
3 サニタイズの回避 多層パーセプトロン(Multilayer perceptron)
Q学習(Q-Learning)
Table 1 使用する機械学習アルゴリズム

 多層パーセプトロンとQ学習は、筆者が以前書いたブログで簡単に説明していますので、今回はLSTMについてのみ説明します。



HTML/JavaScript構文の理解


 LSTMはニューラルネットワークの1種で、従来のニューラルネットワークでは学習が困難だった時系列データ(動画、音楽、音声など)を学習することが可能です。時系列データを学習するためには長期的・短期的なデータ間の依存関係(例:動画の冒頭と序盤、そして終盤との関係など)を考慮する必要がありますが、LSTMはこれを可能にします。この特徴を利用して、音声認識や音楽生成などで利用が広まっています。


 また、文章も一つひとつの単語を一つの時間と捉えると、時系列データとして考えることができます。この特徴を利用して、機械翻訳や文章生成などにも利用されています。


 例えば、太宰治の小説をLSTMで学習させることで、太宰治風のオリジナル小説を自動生成する検証事例(DeepDazai)や、LinuxのソースコードをLSTMで学習させることで、C言語風のソースコードを自動生成する検証事例(Andrej Karpathy blog)、UNIXのコマンド操作ログをLSTMで学習させることで、コマンドライン操作の予測を行った事例(shi3zの長文日記)、などがあります。

 これらは、学習に使用した文章(小説やソースコードなど)中の各文字の依存関係を上手く学習させ、LSTMに文書生成の起点となる文字に続く文字を次々と推測させることで実現しています。

 これらの検証事例は非常に面白い内容になっていますので、ご興味のある方は参照されることをおススメします。


 今回は、このLSTMの文章生成能力を「HTML構文の理解」「JavaScript構文の理解」に利用したいと思います。


 では、どのように利用するのでしょうか?


 先ずはFigure 8をご覧ください。

 入力した値がTEXTAREAタグで囲まれた箇所にエコーバックされています。TEXTAREAタグで囲まれているため、単純にスクリプトを入力してもスクリプトを実行することはできません。


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Case 5 - RXSS</title>
</head>
<body>
<textarea name="in" rows="5" cols="60">testData</textarea>
<p>
</body>
</html>
Figure 8 TEXTAREAタグで囲まれた箇所にエコーバックされた様子

 私がFigure 8を見て思うことは、「TEXTAREAタグを閉じた後にスクリプトを挿入すればいけるかな?」です。そこで、今度は以下の値を入力します(Figure 9)。


http://xxx/case5/?input=</textarea><script>alert('XSS');</script>
Figure 9 TEXTAREAタグを閉じる文字列を入れた様子

 すると、Figure 10のレスポンスが返されます。


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Case 5 - RXSS</title>
</head>
<body>
<textarea name="in" rows="5" cols="60"></textarea><script>alert('XSS');</script></textarea>
<p>
</body>
</html>
Figure 10 TEXTAREAタグで囲まれた箇所にエコーバックされた様子

入力した「</textarea>」によってTEXTAREAタグが閉じられ、「alert('XSS')」を実行させることができました。

 この検査を行うためには、エコーバック箇所の前方にTEXTAREAタグの始点(<textarea name=…)があることを観察し、これをHTML構文に則り、TEXTAREAタグの終点(</textarea>)で閉じることに気付く必要があります。


 今回は、この「<textarea name=…>の後に</textarea>を入れる必要がある」という、何気なく人間が行っている思考をLSTMで実現します。


 なお、RXSSが発現するパターンは上記のみならず、タグ内の属性値コメント内FRAMESETタグ内JavaScript内など様々です。このため、LSTMはあらゆるパターンのHTMLやJavaScript構文を理解する必要があります。


 人間がHTMLやJavaScript構文を理解するためには、参考書を読んだり人から教えて貰ったりしますが、LSTMに参考書を読ませるわけにはいきません。

 そこで、今回はFigure 11のデータを使用して、LSTMにHTMLとJavaScript構文の学習をさせました。このデータは、実際にインターネット上で使用されているHTML構文を無作為に約2万ページ分(12,000パターン)収集したものであり、様々なHTML構文が含まれています。


<abbr class="" data-shorten="" data-utime="" title=""></abbr>
<abbr class="" data-utime="" title=""></abbr>
<abbr class='' title=''></abbr>
<abbr data-utime='' title=''></abbr>
(省略)
<input name="" type="" value=""/>
<input alt="" id="" onclick="" src="" type=""/>
<input alt='' id='' src='' type=''/>
<input alt='' name='' src='' type=''/>
(省略)
<video autoplay="" class="" loop="" muted="" poster=""></video>
<video autoplay="" loop="" muted=""></video>
<video class="" controls="" height="" id="" preload="" width=""></video>
<video src='' tabindex=''></video> <video src=''></video>
Figure 11 HTML構文の学習データ(一部抜粋)

 この学習データを使用して、LSTMに「HTMLのタグや属性などの順序関係」を学習させます。

 なお、学習時間は学習データの量に比例しますので、構文学習にあまり寄与しない属性値やコンテンツは学習前に除外して省エネを図りました。また、インターネット上から無作為に収集したデータのため、誤った構文が含まれていることも考えられますが、全体のデータ量と比較すると極めて少ないため、学習結果には影響はないと考えました。


 なお、私が使用した環境(※)では、学習に約20時間かかりました。

 ※CPU:Core i7 2.5GHz、GPU:GeForce GTX 965M 4GB、Memory:16GB


 この学習により、LSTMはHTML構文の文脈を理解することができ、文書生成の起点になる文字(シード)を与えると、それに続く最適なHTMLを自動生成するようになります。

 Table 2は、学習済みのLSTMに対して様々なシードを与えた結果を示しています。


No シード LSTMが生成した構文
1 <textarea rows="5" cols="60"> </textarea>xxx(適当な文字列)xxx
2 <!-- mbsdtest -->xxx(適当な文字列)xxx
3 <input name="" type="" value=" ">xxx(適当な文字列)xxx
Table 2 LSTMが生成したHTML構文(一例)

 この結果から、シードに対応する後続のHTMLを上手く生成できていることが分かります。


 なお、RXSSを検出する場合は、入力値がエコーバックされる個所を起点にし、前方の何文字かのHTML構文をシードとして抽出してLSTMに与えます。そして、LSTMが生成したHTML構文の後に任意のスクリプトを付与することで、Figure 3やFigure 10のようにHTML構文の流れに沿ってスクリプトを挿入することが可能になります。


 JavaScript構文も同様に学習させます。

 Figure 12はJavaScript用の学習データを示しています。この学習データには約1万ページ分のJavaScript構文が含まれています。


_satellite.pageBottom();']
    (function(window) {
      var _gaq = _gaq || [];
      var methods = ['log', 'dir', 'trace'];

      if (typeof window.console === 'undefined') {
          window.console = {};
      }
      for (var i in methods) {
          if (!(methods[i] in window.console)) {
              window.console[methods[i]] = function() {};
          }
      }
    }(window));
"]
(省略)
Figure 12 JavaScript構文の学習データ(一部抜粋)

 JavaScriptの場合は学習に約28時間かかりました。

 Table 3は、学習済みのLSTMに対して様々なシードを与えた結果を示しています。


No シード LSTMが生成した構文
1 var hoge = ['log', ' red'];\r\n xxx(適当な文字列)xxx
2 /* mbsdtest */ xxx(適当な文字列)xxx
3 function(){ xxx(適当な文字列)xxx }\r\n
Table 3 LSTMが生成したJavaScript構文(一例)

 こちらも、シードに対応する後続のJavaScript構文を上手く生成できていることが分かります。


 このように、実際に世の中で使用されている大量のHTML/JavaScript構文をLSTMの学習データとして利用することで、RXSSを見つけるための要件「HTML構文の理解」「JavaScript構文の理解」を実現することができます。


 次に、3番目の要件である「サニタイズの回避」を実現するアイデアを説明します。



サニタイズの回避


 今回は「多層パーセプトロン(以下、MLP)」と「Q学習(Q-Learning)」を組み合わせたモデルを構築しました(Figure 13)。

 このモデルは、「Webアプリへの入力値がエコーバックされる個所(以下、出力箇所)」と「サニタイズのパターン」を入力に取り、それに対応する一つの検査パターンを出力します。


Figure 13 サニタイズを回避するモデル

 MLPはシンプルな3層構造で、入力層が6ノード(青○)、中間層が100ノード(緑○)、出力層(赤○)は検査パターン数により変化します(現在は20ノード)。

 なお、現時点では出力箇所を以下の5種類に限定しています(今後増やす予定)。


  • "」で囲まれた属性値(<~hoge="xxx">
  • '」で囲まれた属性値(<~hoge='xxx'>
  • 何にも囲まれていない属性値(<~hoge=xxx>
  • JavaScript内(<script type="text/javascript">xxx</script>
  • HTMLタグの外側(<~>xxx</~>

 また、サニタイズパターンは以下5種類の組み合わせにしています(今後増やす予定)。


  • "」のエンティティ参照への変換、または、排除
  • '」のエンティティ参照への変換、または、排除
  • <」のエンティティ参照への変換、または、排除
  • >」のエンティティ参照への変換、または、排除
  • alert();」「script」の排除

 出力層の各ノードは各検査パターンに紐付いており、MLPに入力された「出力箇所とサニタイズパターン」に応じてどれか一つの「検査パターン」が選択されるようになっています。なお、全く意味のない検査(JavaScript内にも関わらずHTMLタグを挿入するなど)を行うのを防ぐため、出力箇所がHTMLとJavaScriptの場合で使用する検査パターンを分離するようにしています(Table 4)。


No 出力箇所 検査パターン(抜粋)
1 "」で囲まれた属性値 "event handler、"><sCriPt>xxx、"><img src=xxx、
'event handler、'><sCriPt>xxx、'><img src=xxx、
SP+event handler、><sCriPt>xxx、><img src=xxx、
<sCriPt>xxx</sCriPt>、<svg/onload=alert()>…
2 '」で囲まれた属性値
3 何にも囲まれていない属性値
4 HTMLタグの外側
5 JavaScript内 ";alert();//、[CR][LF]alert();、\";alert();//…
Table 4 出力箇所毎の検査パターン(一例)

 このような入出力値を使用することで、以下のような人間っぽい思考を実現することができます。


 「(Webアプリへの)入力値がINPUTタグのVALUE属性値にエコーバックされているが、「'」「>」「<」はサニタイズされている。この場合は「"onmouseout=alert();」が使えそうだから、これを試してみよう。」


 しかし、MLPは初期状態ではまったく学習されていないため(重みが最適化されていない)、このままでは上記の思考は実現できず、ただランダムに検査パターンを選択してしまいます。


 そこで、何らかの方法でMLPを学習させる必要があります。通常、MLPを学習させる場合は、教師データ(MLPの入出力ペア)を使用してMLPの重みを最適化していきますが、今回は予め教師データ(MLPへの入力に対する最適な検査パターン)を知る由もありませんので、この方法は採用できません。


 そこで今回はQ学習を使用することにしました。

 Q学習は強化学習の一手法で、イメージ的には「人間が成功と失敗を繰り返しながら徐々に賢くなっていく」様を表現することができます。


 学習の流れは以下のとおりです。


  1. 「出力箇所とサニタイズパターン」をMLPに入力。
  2. MLPが何らかの「検査パターン」を選択。
  3. 2で選択された検査パターンを用いて、検査対象Webアプリにリクエスト送信。
  4. レスポンスを解析し、RXSSの検出有無を確認。
  5. 検出有無に応じてQ値(※)を更新し、更新前後のQ値の誤差を算出。
    ⇒RXSS検出時はQ値を大きく更新、未検出の場合は小さく(または負)更新。
  6. 誤差が最小になるように、MLPの重みを更新。
※Q値…検査パターンの価値。
    RXSS検出に寄与する検査パターンほどQ値が大きくなる。

 上記の処理を100回程度繰り返すことで、徐々にMLPの学習が進んでいきます。

 このような仕組みをSAIVSに組み込むことで、学習初期はランダムな検査パターンを選択していたSAIVSが徐々に賢くなっていき、学習の終盤にはサニタイズを回避するような最適な検査パターンを優先的に選択するようになります。



SAIVSを訓練する


 検査の本番時にSAIVSの学習(100回程度の試行)を行うと、非常に検査の効率が悪くなります。そこで今回は、事前前にSAIVSの訓練を行うことにしました。

 訓練を行うには「いい感じ」のRXSSパターンが含まれたWebアプリが必要ですが、今回はWAVSEPを使用することにしました。WAVSEPには様々なサニタイズが施されたRXSSパターンが多く含まれているため、これらのパターンに対してSAIVSが繰り返し検査を試行することで効果的に訓練を行うことができると考えました。


 Table 5に今回使用した訓練ケースの一例を示します。


WAVSEP - ReflectedXSS GET Input Vector
Case06-Event2TagScope.jsp 出力箇所 :IMGタグのSRC属性値("
サニタイズ:「<」「>」をエンティティ参照に変換
回避例  :"onmouseover=alert(3122);"
Case08-Event2SingleQuotePropertyScope.jsp 出力箇所 :IMGタグのSRC属性値('
サニタイズ:「<」「>」をエンティティ参照に変換
回避例  :'onmouseover=alert(3122);'
Case09-SrcProperty2TagStructure.jsp 出力箇所 :SCRIPTタグのID属性値(unquote)
サニタイズ:「"」「'」「<」「>」をエンティティ参照に変換
回避例  :SP + src=saivs.js(任意のPath)
Case10-Js2DoubleQuoteJsEventScope.jsp 出力箇所 :SCRIPTタグのonClick値('
サニタイズ:「"」「<」「>」をエンティティ参照に変換
回避例  :';alert(3122);//
Case11-Js2SingleQuoteJsEventScope.jsp 出力箇所 :SCRIPTタグのonClick値("
サニタイズ:「'」「<」「>」をエンティティ参照に変換
回避例  :";alert(3122);//
Case12-Js2JsEventScope.jsp 出力箇所 :SCRIPTタグのonClick値(unquote)
サニタイズ:「"」「'」「<」「>」をエンティティ参照に変換
回避例  :;alert(3122);//
Case27-Js2ScriptTagOLCommentScope.jsp 出力箇所 :JavaScriptの単行コメント内(//)
サニタイズ:コメントアウト
回避例  :[CR][LF]alert(3122);//
Table 5 訓練に使用したケース

 各Caseに対して約100回の検査試行を行い、「出力箇所とサニタイズパターン」に対する「最適な検査パターン」の学習を行いました。この訓練の結果、SAIVSは様々なサニタイズ(少なくともWAVSEPの)を回避する最適な検査パターンを優先的に選択することができるようになりました。


 しかし、本番時にTable 5の訓練ケースに含まれていない「未知のサニタイズパターン」に遭遇した場合は、これを回避することができません(=RXSSを検出できない)。

 現在の運用では、未知のサニタイズパターンに遭遇した場合は、事後に私が検証を行った上でSAIVSにレクチャ(再訓練)するようにしています。これにより、次回からは検出できるようになります(検査経験を積めば積むほど賢くなります)。



SAIVSの検査フロー


 これまでの説明を纏めると、SAIVSは以下のフローでRXSSの検出を試みます。

 なお、LSTMとMLPの学習は完了しているものとします。


- 第1次検査 -

  1. 検査対象Webアプリのパラメータに適当な入力値を設定してリクエストを送信。
  2. レスポンスを解析し、入力値の出力箇所を特定
  3. 出力箇所を基にシードを抽出
  4. シードに続くHTML/JavaScriptを自動生成し、末尾にスクリプトを挿入
  5. 4の文字列を同パラメータに設定して再度リクエストを送信。
  6. レスポンスを解析し、RXSS有無を判定(検出⇒終了、未検出⇒7)。
    併せて、サニタイズのパターンを特定

- 第2次検査 -

  1. 出力箇所とサニタイズパターンを基にサニタイズ回避可能な検査パターンを選択
    ※優先順位の高いものから順に選択。
  2. 7の文字列を同パラメータに設定して再度リクエスト送信。
  3. レスポンスを解析し、RXSS有無を判定(検出⇒終了、未検出⇒7)。

 上記のとおり、脆弱性の検出は2つの工程「第1次検査」「第2次検査」で行います。

 第1次検査は、入力値がエコーバックされる個所を観察した上で、学習済みのLSTMを使用してHTMLまたはJavaScriptの構文に則ったスクリプトの挿入を試みます。

 サニタイズが無いアプリの場合は、この1回の試行でRXSSを検出することができます。


 第2次検査は、サニタイズによって第1次検査でRXSSを検出できなかった場合に行われます。出力箇所とサニタイズパターンを基に、学習済みのMLPが選択したサニタイズを回避する検査パターンを使って検査を行います。

 訓練で経験したことのあるサニタイズパターンの場合は、この1回の試行でRXSSを検出することができます。

 1回目の試行で検出できない場合は、過去の経験を基にSAIVSが把握している検査パターンを優先順に試行していきます。なお、検査時間短縮の観点から、10回試行してRXSSを検出できない場合は諦めるようにしています。


 このように、SAIVSは最小で1~2回程度、最大で11回の試行でRXSSを検出することができるように設計されています。


 次章では、SAIVSがRXSSを検出するデモをお見せします。



4. デモ


[練習] vs Webseclab


 先ずは肩慣らしとして、Yahooのエンジニアが開発した脆弱性スキャナ用の検証環境「Webseclab」に対して検査を行います。この検証環境には様々なRXSSのパターンが含まれていますが、今回は以下の4ケースを使用しました。


No Case名称 サニタイズ Case概要
1 /xss/reflect/full1 × 入力値がBODYタグ内にエコーバック。
2 /xss/reflect/textarea1 × 入力値がTEXTAREAタグ内にエコーバック。
3 /xss/reflect/onmouseover 入力値がINPUTタグの属性値にエコーバック。
入力値からタグ閉じ(</xxx>)が削除される。
4 /xss/reflect/js4_dq × 入力値がSCRIPTタグ内にエコーバック。
Table 6 練習用のケース

 なお、「Webseclab」は画面遷移を必要とせず、対象のページに直接アクセスすることで各Caseの検証を簡単に行うことができます。

 以下、Case毎の検証結果を示します。



Case1. 出力箇所:BODYタグ内、サニタイズ:無


 入力値はBODYタグ内にエコーバックされ、サニタイズはありません。

 Figure 14は、正常時の入力値とレスポンス内容を示しています。


http://xxx/xss/reflect/full1?in=saivs12345
-----------------------------------------------------------------------
<!doctype html><html>
<head><title>Full Javascript Injection (full.1)</title></head><body>

Hello!<BR>
The value of cgi parameter &quot;in&quot; is: saivs12345

</body></html>
Figure 14 Case1:正常時のレスポンス

 これに対して、RXSSを検出した際の様子をFigure 15、Movie 1に示します。

 入力値前方の「lasther=''></form>」は、SAIVSがFigure 14のレスポンス内容を見て自動生成した文字列です(Movie 1の「generating text…」で生成)。


http://xxx/xss/reflect/full1?in=lasther=''%3E%3C/form%3ED0i7Q%22VW53N'nT7t0%3Cscript%3Ealert(3122);kc5i3%3C/script%3EueFj8
-------------------------------------------------------------------------
<!doctype html><html>
<head><title>Full Javascript Injection (full.1)</title></head><body>

Hello!<BR>
The value of cgi parameter &quot;in&quot; is: lasther=''></form>D0i7Q"VW53N'nT7t0<script>alert(3122);kc5i3</script>ueFj8

</body></html>
Figure 15 Case1:検査時のレスポンス

Movie 1 Case1:検査時の様子

 Case1は単純なパターンのため、1回の試行でRXSSを検出することができました。

 これは序の口です。



Case2. 出力箇所:TEXTAREAタグ内、サニタイズ:無


 入力値はTEXTAREAタグ内にエコーバックされ、サニタイズはありません。

 Figure 16は、正常時の入力値とレスポンス内容を示しています。


http://xxx/xss/reflect/textarea1?in=saivs12345
-------------------------------------------------------------------------
<!doctype html><html><head><title>Reflected XSS in textarea (textarea1)</title></head><body>

<H2>Textarea injection test</H2>
This test requires a closing textarea tag to break out of the field context and trigger a subsequent exploit.
<p>
<FORM>
<textarea name="in" rows="5" cols="60">saivs12345</textarea>
<p>
<INPUT type="Submit">
(省略)
Figure 16 Case2:正常時のレスポンス

 これに対して、RXSSを検出した際の様子をFigure 17、Movie 2に示します。

 入力値前方の「</textarea>」は、SAIVSがFigure 16のレスポンス内容を見て自動生成した文字列です(Movie 2の「generating text…」で生成)。


http://xxx/xss/reflect/textarea1?in=%3C/textarea%3E7Q7pN%22MBPcc'PA6tz%3Cscript%3Ealert(3122);WKr8J%3C/script%3EfowCP
-------------------------------------------------------------------------
<!doctype html><html>
<head><title>Reflected XSS in textarea (textarea1)</title></head><body>

<H2>Textarea injection test</H2>
This test requires a closing textarea tag to break out of the field context and trigger a subsequent exploit.
<p>
<FORM>
<textarea name="in" rows="5" cols="60"></textarea>7Q7pN"MBPcc'PA6tz<script>alert(3122);WKr8J</script>fowCP</textarea>
<p>
<INPUT type="Submit">
(省略)
Figure 17 Case2:検査時のレスポンス

Movie 2 Case2:検査時の様子

 TEXTAREAタグが(SAIVSが生成した)「</textarea>」で閉じられ、SCRIPTタグが挿入できていることが分かります。

 こちらも1回の試行でRXSSを検出しています。



Case3. 出力箇所:INPUTタグのVALUE属性値、サニタイズ:有


 入力値はINPUTタグのVALUE属性値にエコーバックされ、また、タグ閉じ(</xxx>)が排除される仕様になっています。

 Figure 18は、正常時の入力値とレスポンス内容を示しています。


http://xxx/xss/reflect/onmouseover?in="><script>alert()</script>
-------------------------------------------------------------------------
<!doctype html><html><head><title>Reflected XSS - attribute injection in tags (dq.2)</title></head><body>

<H2>Update Your Preferences</H2><p>
<FORM>
Homepage: <input value=""><script>alert()" name="in" size="40"><BR>
<input type="submit" value="Change"></FORM>

</body></html>
Figure 18 Case3:正常時のレスポンス

 入力したSCRIPTタグ閉じ(</script>)が排除され、スクリプトが動作しないようになっています。


 これに対して、RXSSを検出した際の様子をFigure 19、Movie 3に示します。

入力値前方の「"></option><option s」は、SAIVSがFigure 18のレスポンス内容を見て自動生成した文字列です(Movie 3の「generating text…」で生成)。


http://xxx/xss/reflect/onmouseover?in=%22%3E%3C/option%3E%3Coption%20s%20onmouseover=alert(3122);//
-------------------------------------------------------------------------
<!doctype html><html><head><title>Reflected XSS - attribute injection in tags (dq.2)</title></head><body>

<H2>Update Your Preferences</H2><p>
<FORM>
Homepage: <input value=""> <option s onmouseover=alert(3122);//" name="in" size="40"><BR>
<input type="submit" value="Change"></FORM>

</body></html>
Figure 19 Case3:検査時のレスポンス

Movie 3 Case3:検査時の様子

 Movie 3を見ると、最初は「">%lt;/option>%lt;option … %lt;script>alert(3122);…%lt;/script>」のパターンを使って検査していますが、サニタイズによりRXSSが検出できなかったため、次にサニタイズを回避する検査パターンを次々と試しています。これは過去の訓練を基に優先順位の高いものから順に選択しています。

 そして、2回目の試行でFigure 19に示す検査パターンを使い、RXSSを検出しています。

 上手くサニタイズを回避していることが分かります。



Case4. 出力箇所:JavaScript内、サニタイズ:無


 入力値はSCRIPTタグ内にエコーバックされ、サニタイズはありません。

 Figure 20は、正常時の入力値とレスポンス内容を示しています。


http://xxx/xss/reflect/js4_dq?in=saivs12345
-------------------------------------------------------------------------
<!doctype html><html><head><title>JavaScript and double-quote injection in JS block (js.4)</title></head><body>

 <script language="javascript">
var f = {
        date: "",
        week: "1",
        bad: "saivs12345",
        phase: "2",
      };
</script>
(省略)
Figure 20 Case4:正常時のレスポンス

 これに対して、RXSSを検出した際の様子をFigure 21、Movie 4に示します。

入力値前方の「6",[CR][LF]SP」は、SAIVSがFigure 20のレスポンス内容を見て自動生成した文字列です(Movie 4の「generating text…」で生成)。


http://xxx/xss/reflect/js4_dq?in=6%22,%0A%20%20%20%20%20%20%20%20%20%20%20%20skuI;alert(3122);//1VU7k
-------------------------------------------------------------------------
<!doctype html><html><head><title>JavaScript and double-quote injection in JS block (js.4)</title></head><body>

 <script language="javascript">
var f = {
        date: "",
        week: "1",
        bad: "6",
            skuI;alert(3122);//1VU7k",
        phase: "2",
      };
</script>
(省略)
Figure 21 Case4:検査時のレスポンス

Movie 4 Case4:検査時の様子

 オブジェクトリテラルのキー「bad」のデータが(SAIVSが生成した)「",」で閉じられ、改行後に「alert(3122);//」を挿入していることが分かります。そして、こちらも1回の試行でRXSSを検出しています。

※Figure 21の状態ではスクリプトは動作しませんが、「"」「,」「//」「alert(3122);」が挿入できることからRXSS有りとして判定しています。



[実戦] vs Google gruyere


 次に、ちょっと実戦的な検証として、Googleが開発した脆弱性診断員の練習に使用されるWebアプリ「Google gruyere」に対して検査を行います。このWebアプリには様々な機能が備わっていますが、今回は以下の2機能を対象にしました。


No Case名称 サニタイズ Case概要
1 New Snippet ・データ(snippet)を登録する機能。
・事前にログインが必要。
・入力値がBODYタグ内にエコーバック。
・「<script></script>」がサニタイズされる。
2 New Snippet ・プロフィールを編集する機能。
・事前にログインとプロフィール登録が必要。
・入力値がAタグ内にエコーバック。
・「<」「>」がサイニタイズされる。
Table 7 検査対象の機能

 なお、「gruyere」は「Webseclab」と異なり、対象の機能にアクセスするためには「ログイン画面⇒ログイン処理⇒ログイン後トップページ…」のように、事前に複数の画面を遷移する必要があります。また、画面遷移は単純にリンクを踏むだけではなく、入力フォームに適切な値を入力しながら遷移する必要があります(ログイン、プロフィール登録など)。今回はこの画面遷移の実現に、以前筆者が書いたブログで説明した「機械学習でWebアプリをクローリングする」技術を使用しています。


 このように、SAIVSは「検査対象機能までクローリングしてから検査を行う」という一連の行為を全て自動で行うことができます。


 以下、機能毎の検証結果を示します。



New Snippet. 出力箇所:BODYタグ内、サニタイズ:有


 入力値はBODYタグ内にエコーバックされ、「<script></script>」が削除されます。

 また、本機能にアクセスするために、事前にログインを行う必要があります。


 Figure 22は、正常時の入力値とレスポンス内容を示しています。


http://xxx/1142014131/newsnippet2?snippet=saivs12345
-----------------------------------------------------------------------
(省略)
            <td valign='top'>
              <a href='/1142014131/deletesnippet?index=20'>[X]</a>&nbsp;
            </td>
            <td valign='top'>
              <div id='20'>
                saivs12345
              </div>
            </td>
          </tr>
(省略)
Figure 22 正常時のレスポンス

 これに対して、RXSSを検出した際の様子をFigure 23、Movie 5に示します。


http://xxx/1142014131/newsnippet2?snippet=widtt=''%3E%3C/option%3E%20onmouseover=alert(3122)
-------------------------------------------------------------------------
(省略)
            <td valign='top'>
              <a href='/1142014131/deletesnippet?index=0'>[X]</a>&nbsp;
            </td>
            <td valign='top'>
              <div id='0'>
                widtt=''></option> onmouseover=alert(3122)
              </div>
            </td>
          </tr>
(省略)
Figure 23 検査時のレスポンス

Movie 5 検査時の様子

 Movie 5を見ると、最初は「''></option> … <script>alert(3122);…</script>」のパターンを使って検査していますが、サニタイズによりRXSSが検出できなかったため、次にサニタイズを回避する検査パターンを試しています。

 そして、2回目の試行でFigure 23に示す検査パターンを使い、RXSSを検出しています。

上手くサニタイズを回避していることが分かります。

※Figure 23の状態ではスクリプトは動作しませんが、任意のタグ(<option>)やイベントハンドラ(onmouseover)、スクリプト(alert(3122))が挿入できることからRXSS有りとして判定しています。



Profile. 出力箇所:Aタグ内、サニタイズ:有


 入力値はAタグ内のHREF属性値にエコーバックされ、「<」「>」がエンティティ参照に変換されます。

 また、本機能にアクセスするために、事前にログインとプロフィール登録を行う必要があります。


 Figure 24は、正常時の入力値とレスポンス内容を示しています。


http://xxx/1142014131/saveprofile?action=update&name=test&oldpw=&pw=&icon=hoge2&color=hoge4&private_snippet=hoge5&web_site=saivs12345
-----------------------------------------------------------------------
(省略)
    <br>
      <a href='/1142014131/snippets.gtl?uid=test'>All snippets</a>&nbsp;
      <a href='saivs12345'>Homepage</a>
    <br>
    <br>
    </td>
(省略)
Figure 24 正常時のレスポンス

 これに対して、RXSSを検出した際の様子をFigure 25、Movie 6に示します。


http://xxx/1142014131/saveprofile?action=update&name=test&oldpw=&pw=&icon=hoge2&color=hoge4&private_snippet=hoge5&web_site='%20onloc=''/%3E%3Chr%20alt=%20onmouseover=alert(3122)
-------------------------------------------------------------------------
(省略)
    <br>
      <a href='/1142014131/snippets.gtl?uid=test'>All snippets</a>&nbsp;
      <a href='' onloc=''/&gt;&lt;hr alt= onmouseover=alert(3122)'>Homepage</a>
    <br>
    <br>
    </td>
(省略)
Figure 25 検査時のレスポンス

Movie 6 検査時の様子

 Movie 6を見ると、最初は「' onloc=''/><hr alt= … <script>alert(3122);…</script>」のパターンを使って検査していますが、サニタイズによりRXSSが検出できなかったため、次にサニタイズを回避する検査パターンを試しています。

 そして、2回目の試行でFigure 25に示す検査パターンを使い、RXSSを検出しています。

上手くサニタイズを回避していることが分かります。

※Figure 25の状態ではスクリプトは動作しませんが、シングルクォート「'」やイベントハンドラ(onmouseover)、スクリプト(alert(3122))が挿入できることからRXSS有りとして判定しています。



5. 今後の展望


 今回は機械学習を使ってRXSSを検出するアイデアを紹介しました。そして、比較的単純なRXSSであれば、少ない手数でこれを検出できることを示しました。

 今後はより複雑なRXSS(環境依存や文字コードに起因するRXSS、二重デコードやUnicodeエスケープシーケンス変換などの特殊なサニタイズなど)に対応できるよう、SAIVSのロジックを強化していきたいと考えています。併せて、上記で挙げた課題も克服していきたいと考えています。


 また、Webアプリの脆弱性はRXSS以外にも多く存在します(SQLiやロジック系など)。今後はこれらについても機械学習を始めとした様々な技術を駆使しながら自動検出できるようにしていきたいと考えています。


 最後に、今回は機械学習を使用しましたが、機械学習以外の方法でより効率的・確実にRXSSを検出できる方法が存在するかもしれません。機械学習はあくまでも手段であり、目的ではありません。よって、何らかのタスクに取り組む際、機械学習に固執するのではなく、様々な技術を俯瞰しながら、目的の実現に寄与する技術を組み合わせていく必要があると考えています。


 とは言え、機械学習は非常に面白い技術ですので、遊びでも良いので一度は触ってみることを強くお勧めします!


6. 参考文献

[1] わかるLSTM ~ 最近の動向と共に

[2] LSTMネットワークの概要

[3] Andrej Karpathy blog

[4] RNNによる学習で文豪っぽいテキストを出力させる (aka DeepDazai)

[5] shi3zの長文日記

プロフェッショナルサービス事業部
高江洲 勲