$ cat writeup.md…
$ cat writeup.md…
broncoctf2026
Task: netcat service authorizes commands by md5(input) in an allowlist but dispatches by string equality, printing the flag only in an unreachable else branch when blorgs==468. Solution: register a Wang MD5-collision block as the program name (adding its hash to the allowlist), then send the OTHER colliding block — same MD5 so it authorizes, but a different string that falls through to the flag branch after arithmetic path-planning hits exactly 468 in 3 edits.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
Blorgs! Blorgs everywhere! Can you convince the Blorg Master to hand over his flag?
sc broncoctf-blorg.chals.ionc 0.cloud.chals.io 13758
A netcat service maintains an integer blorgs = 1 that doubles after each accepted
response. The goal is to reach exactly TARGET = 468 blorgs within MAX_EDITS = 3
edits, then get the service to print the flag.
The service (checker.py) authorizes each input by hashing it and looking the hash up
in an allowlist, then dispatches it by string equality:
valid = { "11198b294adbcf089f9d27990258fd22", # increase "b0fe2606af48e49fd3746844798eb6a0", # decrease "334c4a4c42fdb79d7ebc3e73b517e6f8", # none "dbd73c2b545209688ed794c0d5413d5a", # program "a9c449d4fa44e9e5a41c574ae55ce4d9", # quit "A7DD12B1DAB17D25467B0B0A4C8D4A92", # (intended) show } def handle_input(bytes_in: bytes): select = hashlib.md5(bytes_in).hexdigest() if select not in valid: print("That is not a real command!") return user_in = bytes_in.decode("latin-1") if user_in == "increase": blorgs = (blorgs + 1) * 2; edits += 1 elif user_in == "decrease": blorgs = (blorgs - 1) * 2; edits += 1 elif user_in == "none": blorgs *= 2 elif user_in == "program": # define program name + sub-commands, update valid ... elif user_in == program: # run each sub-command recursively ... elif user_in == "quit": exit() else: # <-- the ONLY place the flag is printed if blorgs == TARGET: print(f"Wow! You earned the flag: {FLAG}")
Two bugs stand out:
Bug 1 — the intended show command is unreachable. Its MD5 is stored in the
allowlist as UPPERCASE (A7DD12B1DAB17D25467B0B0A4C8D4A92), but
hashlib.md5(...).hexdigest() always returns lowercase. So show never passes the
select in valid check. Even if it did, there is no elif user_in == "show" branch, so
it would fall into the else.
...
$ grep --similar