SECCON2019にチーム「MIS.W」として参加しました。最終結果は、456ポイント獲得して、148位でした。途中参戦でしたが1問解けてよかったです。
特に担当を決めてなかったのですが、去年のweb問題担当が今年はいなかったので、僕はぼんやりweb問題を見ることにしていました。Cryptoは僕の研究分野なので精進して来年は倒せるようにしたい…
取り組んだ問題
[web] Option-Cmd-U
index.php?action=source
にアクセスするとページのソースが見られる。
<?php if ($_GET['action'] === "source"){ highlight_file(__FILE__); die(); } ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Option-Cmd-U</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css"> <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script> </head> <body> <div class="container"> <section class="hero"> <div class="hero-body"> <div class="container"> <h1 class="title has-text-centered has-text-weight-bold"> Option-Cmd-U </h1> <h2 class="subtitle has-text-centered"> "View Page Source" is no longer required! Let's view page source online :-) </h2> <form method="GET"> <div class="field has-addons"> <div class="control is-expanded"> <input class="input" type="text" placeholder="URL (e.g. http://example.com)" name="url" value="<?= htmlspecialchars($_GET['url'], ENT_QUOTES, 'UTF-8') ?>"> </div> <div class="control"> <button class="button is-link">Submit</button> </div> </div> </form> </div> </div> </section> <section class="section"> <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 --> <?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 ) )))); } } ?> </pre> </section> </div> </body> </html>
コメントから、このシステムはdocker-composeにより立ち上げられたdockerコンテナ上で動いていることがわかる。
さらに、/docker-compose.yml
にアクセスすると使われているdocker-composeが降ってくる
version: '3' services: nginx: (...ommitted...) php-fpm: (...ommitted...
そういうわけなので、雑にhttp://nginxにアクセスしようとすると当然以下の行のバリデーションで弾かれる。
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?';
検索ボックスにhttp://ocu.chal.seccon.jp:10000/flag.php
を入れてみると、docker0の内部IPがわかる。
Forbidden.Your IP: 172.25.0.1
ということは、このコンテナのcidrは172.25.0.1/24だと思われるので、nginxとphp-fpmはそれぞれ172.25.0.2``172.25.0.3
のいずれかが割当てされているはず。
一つずつ検査していくと、172.25.0.3
のときに、http://nginx/index.php
を入力した時と同じエラーが出る。これがnginxコンテナの内部IPアドレスであることがわかった。
ここで、バリデーションに用いられているgethostbyname
だが、PHPのドキュメントを見ると以下のように書いてある。
gethostbyname — インターネットホスト名に対応するIPv4アドレスを取得する https://www.php.net/manual/ja/function.gethostbyname.php
ということは、IPv6アドレスを投げればバリデーションを通過できるのではと考える。
IPv6には、IPv4に対応するために、「IPv4射影アドレス」という概念が存在する。
IPv4射影アドレスは、IPv6ノードが、IPv4しかサポートしていないノードと通信する際に使用されるアドレスです。こちらも利便性を考え、IPv4アドレス部分は10進数表記のままとされています。 https://www.atmarkit.co.jp/ait/articles/1107/19/news122_2.html
この場合、172.25.0.3
のIPv4射影アドレスは、::ffff:172.25.0.3
となる。
つまり、この問題は、射影IPv4アドレスに変換したnginxコンテナの内部アドレスを指定してあげればいいということになる。
得られるflagは、SECCON{what_a_easy_bypass_314208thg0n423g}
[web] web_search
'
というクエリを投げるとErrorになるので自明にSQLi問題。
本文に対してLIKE句によるあいまい検索を行っているような挙動を見せていた。
いくつかクエリを試していると、SQLi対策のためにキーワードベースのフィルターが動いている気がしてきた。恐らくpreg_match系の関数でクエリからSQLi関係のキーワードを弾いているものと思われる。 具体的には以下のものはクエリ内容から潰される。(遷移後のテキストボックス内に残っているものが実行されたクエリと思われる)
- AND
- OR
- %
- comma
- white space
例によってUNION SELECT
のinjectでinformation_schema.tables
を取ってきたかったのだがどう頑張ってもErrorになってしまって困ってしまった。
white spaceは/**/
でエスケープすればいいのだが、commaのfilterをいい感じにbypassする方法が思いつかずにお手上げ
[web] SPA
Vue.js製のSPAで、adminに問い合わせるページくらいしかsubmitできる場所がない。
SECCON2019のページにFlagらしきものが隠されてるので、恐らくセッション情報を悪用してFlagを見られるようにするという問題な気がする。
steal the cookieという文言が問題文にあるので、XSSか何かをしてCookieを奪取せよということなんだろう、というあたりまで調べて放置。
[web] fileserver
終盤にちょっと見たのですが、もう少し時間をかけたかった…
感想
WebのSQLiとXSSは確実に解けるようにしたい。あと来年はCryptoを倒しに行きたい。
頑張るぞ