SECCON CTF quals 2019に参加した

お前は何日まえの日記を書いているんだ?と言われそうだが一応足跡がわりに書き残しておこうと思って投稿します。

簡単な感想と役割

 ここ最近新しいチームに入って「CTFタノシイ」みたいな感じになってきたし、SECCONに出てみた。

 チーム内での役割はWeb問題をとく人だった。昨年出場した際は別のチームで何一問も解けず、時間とただただ溶かし人権がない状態になり最終的には誰もお前を愛さない状態になってしまっていたので、「今年もそうなるんかなー?」みたいなお気持ちでした。

 結果として一問解けたし、楽しい問題をプレイできたので満足しています。 f:id:oukasakura3:20191023200557p:plain

WriteUp

Option-Cmd-U

問題文

No more "View Page Source"!

http://ocu.chal.seccon.jp:10000/index.php

ほーん、便利やん。と言う感想から問題を解き始めた。

解法

該当ページにアクセスするとこんな感じにフォームがあったf:id:oukasakura3:20191023201712p:plain

URLを入れると"Option-Cmd-U"のようにHTMLを表示してくれるのだろうと思い入力。

するとこのようなコメントが見えるではないか。

<pre>
<!-- src of this PHP script: /index.php?action=source -->
<!-- the flag is in /flag.php, which permits access only from internal network :-) -->
<!-- this service is running on php-fpm and nginx. see /docker-compose.yml -->
</pre>

嬉しいことにソースコードが表示できるようなので表示してみる。

<?php
  if (isset($_GET['url'])){
        $url = filter_input(INPUT_GET, 'url');
        $parsed_url = parse_url($url);                        
        if($parsed_url["scheme"] !== "http"){
            // only http: should be allowed. 
            echo 'URL should start with http!';
        } else if (gethostbyname(idn_to_ascii($parsed_url["host"], 0, INTL_IDNA_VARIANT_UTS46)) === gethostbyname("nginx")) {
            // local access to nginx from php-fpm should be blocked.
            echo 'Oops, are you a robot or an attacker?';
        } else {
            // file_get_contents needs idn_to_ascii(): https://stackoverflow.com/questions/40663425/
            highlight_string(file_get_contents(idn_to_ascii($url, 0, INTL_IDNA_VARIANT_UTS46),
                             false,
                             stream_context_create(array(
                             'http' => array(
                                 'follow_location' => false,
                                 'timeout' => 2
                             )
                         ))));
        }
    }
                    ?>

事前に得たコメントアウトからの情報(permits access only from internal network)とコードを見た感じ、Flag入手の方法はhttp://[nginx hostname]:xxxx/flag.phpとなりそうなので、/docker-compose.ymlを見てdocker-compose内でのhostを確認するのが一番手っ取り早そう。

と私の頭の中ではならなかった。

確かに/docker-compose.ymlは見た。そこで私は何個のコンテナが動いているのか確認して終わってしまった。

私が次にとった行動が、/flag.phpへのアクセスだ。 アクセスをすると次のように自分のIPが表示される。

Forbidden.Your IP: xxx.xxx.xx.x

なのでひとまず先ほどのフォームにぶち込んでコンテナのIPを取得

Forbidden.Your IP: 172.25.0.1

取得後、このIPが実際にこのコンテナのものなのか確認するためにhttp://172.25.0.1:10000/flag.phpを入力し、このコンテナのIPであることを確証する。

docker-composeで動いているコンテナ数が2個だったので+-2くらいでNginxのIPにたどり着くと思いアドレスを加算、http://172.25.0.3:10000/flag.phpにアクセスした際に Oops, are you a robot or an attacker?が表示されたので、このIPがNginxのIPであると確信する。

次にif (gethostbyname(idn_to_ascii($parsed_url["host"], 0, INTL_IDNA_VARIANT_UTS46)) === gethostbyname("nginx")) {の突破をしなくてはならない。このif文内できになるのはidn_to_ascii($parsed_url["host"], 0, INTL_IDNA_VARIANT_UTS46)の部分

if (isset($_GET['url'])){
        $url = filter_input(INPUT_GET, 'url');
        $parsed_url = parse_url($url);                 
~~~中略~~~~
        } else if (gethostbyname(idn_to_ascii($parsed_url["host"], 0, INTL_IDNA_VARIANT_UTS46)) === gethostbyname("nginx")) {
            // local access to nginx from php-fpm should be blocked.
            echo 'Oops, are you a robot or an attacker?';
        } else {
~~~後略~~~

この$parsed_urlはidn_to_asciiを通す前にパースする前にidn_to_asciiを通しているので、Unicodeの互換文字を利用すれば突破できそう(今年度のBlackHatで発表されたHostSplit: Exploitable Antipatterns in Unicode Normalization を参考に)。

どこを修正すると綺麗に分割できそうかなと考えた結果:の全角文字だと行けることがわかりhttp://172.25.0.3:80/flag.phpをsubmitして

SECCON{what_a_easy_bypass_314208thg0n423g}

Flagゲット!