MonoのWebClientにおけるURI

発端

発端は前の記事にあるように、tkbctf3の問題としてmiocatなるものを出してみたのはいいものの、意図とは異なる脆弱性を作り込んで250点問題が超絶ボーナス問題になりましたよ、というお話しです。

調査

miocatはC#で書かれており、実際の運用ではMonoランタイムで動いていました。というわけでMonoのソースコードを読めば解決です。やったね。

そういうわけでまずはWebClientの実装を読んでみたのですが、怪しい箇所が一発で見つかりました。WebClient.cs#798、privateメソッドであるCreateUri(string)なるメソッドです。DownloadString(string)は、その引数をこのメソッドに渡してDownloadData(Uri)を呼び出します。

try-catchの中で渡されたアドレス(と、baseAddress)を元にUriのインスタンスを作ってCreateUri(Uri)に渡していますが、ここで例外が発生するとreturn new Uri(Path.GetFullPath(address))という恐怖のコードが走ります。

まず例外を発生させる方法ですが、これは簡単でURIとして不正なものを渡してあげればおしまいです。例えばabc://;/etc/passwdはhostname部がパース不能なので例外を吐きます。

System.IO.Path.GetFullPath(string)というのは名前からお察しの通り、与えられたパスをカレントディレクトリからの相対パスと見なして絶対パスに変換します。ここで、例えばカレントディレクトリが/home/kogasa、引数がhttp://etc/passwdだとすると、結果は/home/kogasa/http://etc/passwdになります。要するにhttp:というディレクトリの下にetcディレクトリがあって云々、という形になります。つまるところ連続する/はひとつにまとめられてしまうわけです。先ほどの例外を吐く例でいけば、/home/kogasa/abc://;/etc/passwdになります。

ところでWindowsだとパスに:が入ってると厄介なことになりそうというか、MSDN的にはNotSupportedExceptionが投げられるべきところだと思うのですが、コレ大丈夫なんでしょうか。ぱっと見ただけだとそれらしいコード見当たらなかったんですが……。

※WindowsでGetFullPathhttp://etc/passwdを与えてみたら、ArgumentExceptionで「URLフォーマットには対応していません」みたいなことを言われました。で、abc://;/etc/passwdを与えるとNotSupportedExceptionで「指定されたパスのフォーマットはサポートされていません」と言われました。ちゃんとスキーム見てるんですね。

それはそれとして、そんな感じでフルパスをUriのコンストラクタに与えると、file:スキームのURLになって返ってきます。こうなるとただのローカルに対するディレクトリ操作なので、例えばabc://;/../etc/passwdにするとフルパスは/home/kogasa/abc:/etc/passwdになりますし、もういっこ../を足せば/home/kogasa/etc/passwd——といった感じで../をつけていけば無事に/etc/passwdに辿り着けますね、やったね、ということでした。

Monoの実装自体がヤバいのか、それともWindows以外で動かした結果ああなったのかまでは知りませんが、ひとまずそんな感じになりました。Windowsで動かしてたらあの解法は通らなかったのかなぁとかぼんやり思いながらそもそももっとマシな実装しておけばよかったと反省しきり。

それにしても繰り返しになりますが結構このURLとしてのパース失敗時の回復の仕方がヤバい気がするんですが、これは意図的に.NET Frameworkと異なる実装にしているのか特に理由はないけどこうなってるのか、はてさて。

まとめ

もっと恐ろしいMonoの片鱗を味わったぜ…