自作検査ツール - XSS編

ちょっと時間があいてしまいましたが、自作ツールのXSS検査について書きます。

XSSシグネチャ

検査文字列は基本的には2種類のみです。

□タイプA
XXXXXX【元の値】'"<9 :&(;\qYYYYYY
□タイプB
XXXXXX【元の値】'":&(;\qYYYYYY

※ XXXXXX、YYYYYYはランダムな英数文字列

ツールは、これらの文字列をパラメータとして与えて、HTMLのどのような文脈に出力されるのか(何らかの属性の値なのか、あるいはSCRIPTタグの内側かなど)、そして出力の際にどのようなエンコードやフィルタが施されるのかを調べて、脆弱性が存在する可能性を調べていきます。

例えば、パラメータ値が「'」や「"」で括られていない属性値に出力されるとします。

<INPUT type="text" name="foo" value=【ここに出る】>

この場合、(正確に言うと一部例外はありますが)検査文字列に含まれるスペースがHTMLに出力されるならば、「脆弱性有り」と判定します。スペースが出力されない場合でも、それ以外の手法によってXSSできる可能性が高いため「要注意」とします。

実際の検査では、実際にJavaScriptが動作する検査文字列を探し出す必要がありますが、このツールにはそこまでのロジックは実装されていません。ツールがやるのはXSS脆弱性の可能性が高い箇所を洗い出すところまでで、その後に手動で脆弱性の有無や、実際に攻撃を成功させるための文字列を確定させていく必要があります。

なお、タイプBは、たまに「<」を激しく嫌うアプリがあるために作ったシグネチャです。アプリの入力値チェックを考えると、本当はA,Bの2つだけではなく、もっと細かくシグネチャを分けていく方がよいのですが、実際のところ2つあれば多くのケースはカバーできるように思います。

HTML内の文脈(コンテキスト)

ツールは、HTMLの文脈として以下を認識します。

  1. HTTPヘッダ
    1. RefreshヘッダのURLの先頭部分
    2. LocationヘッダのURLの先頭部分
    3. Content-Typeヘッダ
  2. SCRIPTタグの内側
    1. クォート付け無し/「'」/「"」
    2. JSのリダイレクトURLの先頭部分
    3. JSのwindow.open URLの先頭部分
  3. STYLEタグの内側
    1. クォート付け無し/「'」/「"」
    2. url()などのURLの先頭部分
  4. 属性値
    1. クォート付け無し/「'」/「"」
    2. イベントハンドラ属性
      (SCRIPTタグの内側と同様に、さらに分類)
    3. スタイル属性
      (STYLEタグの内側と同様に、さらに分類)
    4. URI属性
      1. URI属性値の先頭部分
      2. META/refresh URLの先頭部分
      3. javascriptスキーム付きURI属性
  5. その他

判定の方法は文脈毎に異なります。例えば、SCRIPTタグの内側の「'」で括られた部分に値が出力される場合、検査文字列に含まれる「<」「'」「\」等がどう出力されるかによって脆弱性判定が決まりますし、前述のようなクォート付けされない属性値のケースでは、主にスペース文字の有無により判定が決定される、といった具合です。

なお、文脈の特定に際して、HTML/JavaScript/CSS構文解析を行う必要がありますが、きちんとしたParserでやっているわけではありません。そのため、コメント(<!-- -->、/* */、//)を認識しなかったり、想定外のケースでは文脈と判断を誤るなどの問題があります。まあ大抵の場合は問題が無いだろうというレベルのものです。

最終的には個別に目視で確認していくことを前提としているため、ツールには目視確認を支援するための、パラメータ値がレスポンスに出力される箇所を抜き出してハイライト表示する機能があります。

文脈の特定機能は、オープンリダイレクタ検査用のシグネチャでも使用します。

対象パラメータ

以下のパラメータを検査対象とします。

  • GET、POST、Cookie
  • HTTP Header
  • URLパス(mod_rewrite等の使用を想定)
  • レスポンスHTMLから抽出したパラメータ

このあたりは、XSS以外のシグネチャも同じですが、XSSの検査のみ以下に挙げるような検査も実施できます。

A: PATH_INFOの追加
  http://www.example.jp/foo.cgi?x=1
   →http://www.example.jp/foo.cgi/【検査文字列】?x=1

B: GETパラメータの追加
  http://www.example.jp/foo.cgi?x=1
   →http://www.example.jp/foo.cgi?x=1&【検査文字列】=【検査文字列】

パラメータの値が一旦セッション変数やDBに保存され、別のリクエストを送ったときにアプリ内部で使用される(SQL文が発行されたり、HTMLに出力されたりする)ようなケースでの検査も可能です。

実際に検査を行なう場合、リクエストに含まれる全パラメータを検査する必要は薄く、その時間も無いため、デフォルトではツール検査の対象とするパラメータを絞っており、必要であれば手動で追加・削除できるようにしています。

例えば、アプリで滅多に使われないHTTP HeaderやURLパスは、デフォルトでは検査対象外となっており、検査者が指定した場合にのみ検査対象となります。

また、検索結果一覧画面などでは、foo0、foo1 … foo100(あるいはfoo[0]、foo[1] … foo[100])のようなパラメータを多く含むフォームをたまに見ますが、類似のパラメータを全て検査するのはさすがに無駄なので、デフォルトでは類似のパラメータの最初と最後の分だけを検査対象とするようにしています。

DOM Based XSS

ツールにはDOM Based XSSの発見を支援する機能があります。

この機能は、①SCRIPTタグの内側のJSコード、②イベントハンドラURI属性内のJSコード、③<SCRIPT src=...で読み込んでいる外部JSファイル内のコードを一画面にまとめて表示します。

JSコード表示の際には、DOM Based XSSの候補となる箇所をハイライト表示します。

□eval
eval()

□write/innerHTML
.write()
.writeln()
.innerHTML=...
.outerHTML=...

□javascriptスキーム
location.replace()
location.assign()
location.href=...
window.open()

ただし、これら全ての箇所をハイライト表示するとかなりの数になる場合もありますので、簡単なロジックで絞込みを行ないます。

例えば、write()を含む以下のコード例では、固定の文字列をwrite()しているAは脆弱性の候補から取り除き、B,Cのみをハイライト表示します。

A: document.write('<P>'+'foobar','</P>')
B: document.write('<P>foobar<P>'+somevar)
C: document.write('<P>foo'+somefunc()+'</P>')

最近の商用ツールでは、きちんとJavaScriptを解析するのが主流になっているようですので、このツールの機能は商用のツールに比べるとかなり劣っています。しかし、完全に手動でJavaScriptコードを見ていくよりはだいぶ作業は効率的になります。