ディレクトリトラバーサルは、比較的発見が容易な脆弱性です。
検査文字列を検討する際にポイントとなるのは、どのファイルを見に行くか、NULL文字を入れるか、くらいではないかと思います。
UNIX系OS用のシグネチャ
/../../..(省略)/etc/hosts[0x00]【元の値】
ファイルは/etc/hostsとしました。
/etc/hostsは、Linux、Solaris、FreeBSD、AIX、HP-UXなど主要なUNIX OSに存在します(商用UNIXについては手元に環境が無いので、ネット上の情報からそう判断してます)。
検査文字列の先頭を「../」にするか、あるいは「/../」にするかも一つのポイントです。【元の値】が「/foo/bar.txt」のような値であるならば、検査文字列の先頭が「../」だとうまくいかない可能性があります。そのため、検査文字列の先頭は常に「/../」から始めています。
判定はシンプルに、レスポンス内にhostsファイルと思われる内容が含まれている場合、脆弱性ありとします。
Windows系OSのシグネチャ
Windows用のシグネチャも1パターンのみ用意しました。ただし、WindowsはUNIXよりも少々複雑で考慮すべき点が多いです。
/../../..(省略)/$windir/win.ini[0x00]【元の値】
$windirのところは、通常は「windows」とします。しかし、今でもWindowsNTや2000の環境は残っているため、レスポンスヘッダからIISのバージョンが判り、かつそれが5.1未満である場合は「winnt」にします。
ファイルはboot.iniにするか迷いました。ネット上の情報や海外の書籍などを見てもboot.iniと書いているものが多いのですが(win.iniも少しはありました)、boot.iniファイルはPower Users以上の権限を持つユーザしか参照できません。
(Windows 2000 ServerやXP環境でのcacls実行結果) C:\>cacls boot.ini C:\boot.ini BUILTIN\Power Users:R BUILTIN\Administrators:F NT AUTHORITY\SYSTEM:F
したがって、デフォルトのIIS設定でASPなどのアプリケーションを実行している場合には、boot.iniは参照できないことになります。というわけで今回はwin.iniにしました。
また、NULL文字を入れるべきかも迷いました。
Windowsでの検証では、ASP(.NET)でFSO(File System Object)を使ってファイルを読む場合や、Java/Perlなどでファイルを読む場合には、NULLを入れると以降の文字列が無視されました。
一方、ASP.NET上で、FileOpen関数やSystem.IO.StreamReaderクラスを利用してファイルを読み込むと、パスにNULL文字が入っているだけでExceptionが発生します。
NULL入りとNULL無しの2パターンのシグネチャを作ろうかとも思いましたが、余りパターンを増やしたくないので、今のところNULL入りのシグネチャしか作っていません。ASP環境では、どちらを送っても同じ結果になるだろうという理由もあります。
Blindシグネチャ(UNIX/Windows兼用)
このシグネチャは、ディレクトリトラバーサルのBlind版とも言えるようなものです。
(リクエスト1) ./././【元の値】 (リクエスト2) ././0/【元の値】
判定は少々複雑で、まず最初に上記のリクエスト1を送り、元々のレスポンスと類似しているかを調べます。元々のレスポンスと、リクエスト1のレスポンスが類似している場合、リクエスト2を送ってリクエスト1のレスポンスと類似しているかを調べます。
その結果、リクエスト1とリクエスト2のレスポンスが類似して「いない」場合には、「要注意」と判定します(作成したツールの判定結果は、「脆弱性あり」「要注意」「なし」の3段階としています)。
これで何をやっているかというと、検査対象のパラメータがファイルパスとして使用されているかを探っています。
何故このようなシグネチャを作ったかというと、上記のUNIX用あるいはWindows用のシグネチャでは検出できないケースがまま存在するからです。
このシグネチャでPOSITIVEな反応が出た場合は、手動であれこれ試していくことになります。注意すべきはシグネチャでPOSITIVEな結果が出たとしても、パラメータがファイルパスの一部を構成し、なおかつ入力値チェックがそれほどきつくないことが推測できるだけであるということです。要は、利用可能な脆弱性があるとは限らないということです。
特に上記の1,2の場合には、検査者がアプリケーション内のパス構成に関する知識を持っていない場合の打ち手は限られており、下手に粘ると検査時間だけを無駄に消費してしまうことになります。
そういう意味で、このシグネチャで「要注意」であった場合の次の打ち手をシグネチャ化した方がよいのでしょうけども、まだそこまでは作っていません。
検査の危険性
ディレクトリトラバーサルの検査で危険なのは、ファイルの変更や削除を行なう「更新系」のアプリケーションです。
パーミッションなどにもよりますが、最悪の場合、検査文字列を送ることによって、消してはならないファイルを消去したり壊したりすることもあります。
したがって、最悪壊れてもシステムが起動困難になったりしにくいファイルを選ぶのがよいと思います(そういう意味でも、boot.iniは避けた方がよいかもしれません)。
(おまけ)アプリ環境とディレクトリの遡り
ディレクトリ遡りの可能性は、パラメータの値がファイルパスのどの部分に挿入されるかによって変わってきます。
(パターンA) $file = "/path/$p"; (パターンB) $file = "/path/$p.txt"; (パターンC) $file = "/path/foo_$p.txt"; # ファイルを読み込んで出力する (省略) ※「$p」は攻撃者が制御可能な変数です
上記のA,B,Cでは、一般的にA,Bにて遡りが可能です(正確には、Bで遡りが可能かは状況によります)。Cについては、$pに「/../../(省略)」のような値を与えても、その前の「foo_」が邪魔になって遡りができません。
ただし、Windows環境(JavaやASP.NET)や、UNIX環境でもPHPを使っている場合は、Cのパターンでも遡りができました。
プログラム言語環境によって、実際にパスを辿りながらパスの正規化(「../」などの解釈)を実施したり、あるいは正規化をしてからパスを辿るかなどの挙動の違いがあることが判ります。
なお、最後に紹介したBlind的なシグネチャは、Cで遡りが可能なWindowsやPHPの環境であっても、A,Bのパターンにのみ有効です。