tkbctf3 miocat

miocat(みお・きゃっと)はとある伝説の脱ヲタエンジニアが書き上げた伝説のプログラムに着想を得てっていうのはどうでもいいとして、実際のところは.NET Frameworkのとある仕様に関する問題です。でした。以下本来の意図の説明です。

ソースコード

と、その前に。今回のソースコードはtkbctf/archiveに上げておきます。結構問題の核心以外の部分で苦労してたりしますがそれはそれということで。

追記: 上げました -> tkbctf/archive/tkbctf3/web250_miocat

問題の説明

指示されたアドレスにWebブラウザからアクセスすると、タイトルとフォーム(入力フィールド1つとボタン1つ + hiddenのフィールド)のやる気の無いWebページが表示されます。

このフォームに適当なURLを入力して(このURLをターゲットURLと言うこととします)nya!ボタンを押すと、ターゲットURLに対してmiocatサーバーがリクエストを行い、その結果をそっくりそのままレスポンスとして返します(titleの中身だけ少し変わっていますが)。

実際にはhttp://file://など、スキーマ(らしき文字列)が先頭にないとnot acceptableが表示されます。それらしき文字列であればなんでもいいので、abc://でも通ります。また、サーバーは外部へのアクセスが遮断された状態で稼働していたので、http://www.google.comなどをターゲットに指定しても失敗します。

さて、miocatはターゲットURLを上述の「スキーマらしき文字列があるかどうか」のチェックをした後で、それがfile://であるか否かのチェックをします。ここでfile://であった場合もnot acceptableのエラーになります。ところがこれをすり抜ける方法があります。それはMSDNの.NET Framework で文字列を使用するためのベスト プラクティスにも記載されているまさにそのままの方法です。つまり、フォームのhiddenになっていたlocaleの値、およびクエリパラメータに含まれるlocaleの値がそのスレッドのカルチャになっているので、locale=tr-TRなんて値を指定すると見事にトルコ語ロケールに切り替わり、FILE://で始まっているかのどうかの比較をカレントスレッドのロケールで行うためにチェックをすり抜けてしまいます。

ということは、トルコ語ロケールに切り替えてしまえばfile://...であるようなURLでもmiocatがリクエストを出します。これはmiocatが動作するユーザーが読めるファイルは全て読めることを意味しています。なので/etc/passwdを読んでみたり(`/etc/passwdにはヒントというか、「これを読め」という指示が書かれていました)適当に推測してみたりして、結果的にflagファイルを読めればおしまいです。

miocatの仕組みというか中身は以上です。まぁ、.NETのその仕様を知っていれば一瞬で解けてしまうような問題でした。過去2回F#とILを読ませる問題だったので、少し捻った.NET系の問題を出してみようと思った結果コレになりました。

ディレクトリトラバーサル

しかしながら現実とは非情な物でして、ディレクトリトラバーサルの脆弱性を素で作り込んでいました。というか、内部的には上記のURLチェックをした後にWebClientDownloadString(string)を呼んでるだけなんですが、まさかそいつが普通にカレントディレクトリを読みに行くとは全く予想しておらずチェックもしていなかったというのが実際のところです。いやまぁ、普通与えられたURLをそのまま読みに行くなんて無茶苦茶なことはしないと思うので問題にはならないんでしょうけど。

というわけでその場合はどのようにするかと言いますと、hoge://../../../etc/passwdとかなんとかすると/etc/passwdが読めるので後は上記と同じです。file://と同じようにhttp:///etc/passwdとかすると/home/miocat/http:/etc/passwdを読みにいくんですが、この辺りの仕様は正直私もわかってません。WebClientとかUriの実装読めというお告げなんですかねコレ。(この辺は別の記事でやりたいと思います)

スキーマの部分切り落として**訂正: 切り落としてるのではなく、単にディレクトリとして認識されていた模様(つまりabc://etc.../home/miocat/abc://etc...)**カレントディレクトリを基点にしてファイルを読みに行くのが個人的に結構ヤバイ挙動だと思ってるんですがどうなんでしょう……。

エラー処理

あとこれも完全な手落ちでしたがエラーメッセージにフルパスぶち込まれてるのも良くなかったですね。はい。すっごい初歩的なところですが……。

まとめ

普通に安全なプログラムを書いてから穴を開けるべきだったんだろうなぁと思ってます。今までバイナリ(というかC#/IL)を読ませるような問題しか書いてなくて、こういうタイプの問題は初めてだったわけですけど、難しいですね。(ていうかなぜド素人が問題書いてるのか)

それ以外にも、この方針で行こうと決めてから難易度低すぎなのでアレやコレやと思索してみてやっぱり没になったとか、grepが高速化した云々でちょうどトルコ語らへんが話題になって「あっこれヤバくね」とか思ったりとかいろいろありました。

次のtkbctfはどうなるのかわかりませんが、そろそろ筑波大学期待の新人達が私たちよりよっぽど上手くやってくれるんじゃないかなと期待しております。それでは。