Containing Operation Ghostwire
A Go Security Pro booth CTF story · Oct 7, 2026 · @PHtheAdmin
Go Security Pro ran a three-part booth CTF at IWS called Operation Ghostwire, and every incident followed the same loop: contain nine compromised hosts on paper, turn their locations into a URL token, then decode the signal waiting on the other end.
This is the story of those three incidents, worked from a vendor floor fueled by cantaloupe, watermelon, and a table of pastries. The halls were packed, the crowd was running on early coffee and a great FBI keynote, and Legos and a bottle of bourbon were up for grabs a few booths over.
Along the way you get logic puzzles, a bit of token assembly, three classic encodings, and one 404 that turned out to be my horrible typings fault.
How the Challenge Worked
Each incident came on a two-sided sheet of actual paper. The front was a 9x9 "network hunt" grid split into nine colored segments, and the rules were classic Star Battle: one compromised host in every row, every column, and every segment, and no two hosts can touch, not even diagonally.
The back was a "forensic trace" grid with the same coordinates. Every cell held a short fragment, and most of them were noise built from familiar port numbers and protocol names like 22, 443, 445, ssh, dns, and arp.
Only the nine cells under your hosts mattered. Read them top to bottom, join them with no spaces, and you have a 12-character token for gosecuritypro.com/ctf/. That page holds the encoded payload, and the decoded flag is what you show the booth staff.
Incident 01: Trace the Beacon
The trick with these grids is to start with the most cramped segments, because they force everything else. Segment S6 lived entirely in row 1 and S9 lived entirely in column A, which wiped out three of S2's four cells and forced the first host to B2.
From there it fell like dominoes. S4 had one legal cell left at C4, the sprawling S3 segment got squeezed down to D9, and once S7 was boxed into column F, the rest of the columns sorted themselves out.
The payload on the other end was R1NQe1RSQUNFX1RIRV9CRUFDT059. The giveaway was the opening R1NQ, which is what "GSP" always looks like in Base64. That told me the encoding before I decoded a single character.
Flags: the hosts were E1, B2, G3, C4, I5, A6, H7, F8, D9, the token was 79jc29m43awz, and the decoded payload was GSP{TRACE_THE_BEACON}.
That one earned the gift card!
Incident 02: Hack the Planet
Winners couldn't take a second round, so I switched to support mode for my teammate. The grid opened the same way: S7 was stuck in column H, which pushed S2 to I2, and S9 had nowhere left to go but G6.
The payload was TFC{UNPX_GUR_CYNARG}. TFC lining up letter for letter with GSP is the tell for ROT13, where every letter shifts 13 places.
Flags: the hosts were E1, I2, A3, F4, D5, G6, C7, H8, B9, the token was n7n3wwn8mydx, and the decoded payload was GSP{HACK_THE_PLANET}.
A Hackers (1995) reference for a room full of people who grew up on it!? Fantasitc!
Incident 03: Sorry, Dave
The final grid had three tight segments that did all the heavy lifting. S2 sat in column I and S3 was pinned to the bottom right corner, which forced S1 to H5 and S3 to G9. That squeezed S8 into row 3 and left S7 exactly one legal cell at D8.
The payload was JVS{L'p vruub, Gdyh. L'p diudlg L fdq'w gr wkdw.}. This time JVS mapped back to GSP with a shift of 3, the original Caesar cipher.
Flags: the hosts were B1, I2, E3, A4, H5, C6, F7, D8, G9, the token was 86rd9ke822dw, and the decoded payload was GSP{I'm sorry, Dave. I'm afraid I can't do that.}.
HAL 9000 closes out the movie theme nicely. We cracked it fast, but the booth wanted someone new to take the win, which is fair. Spreading the prizes around keeps people coming back, and the fun was in the solve anyway.
The Gotcha Worth Sharing
The one place we got stuck is the part most worth writing down. On Incident 02, the token was 99.9% correct and the puzzle had only one valid answer, yet the URL kept throwing a 404. ALWAYS CHECK YOUR CODE lol!
The instinct in that spot is to assume you misread a fragment and start guessing variations. We tested a few, but the better move was checking the format first. The Mission 1 URL followed the exact same pattern, /ctf/ plus 12 characters, and our token matched it.
So the work was right and the environment was ready. A few minutes later the typo jumps out at us.
The lesson holds well beyond a booth CTF: when you think your answer is good and it still fails, check your typing, you might just need more coffee lol!
What Defenders Should Take From This
It's a fun booth game, but every round maps to something real.
- Encoding is not encryption. Base64, ROT13, and a Caesar shift all fell in seconds. If sensitive data is "protected" by one of these in your environment, it's not protected.
- Known plaintext gives the game away. Every payload started with a predictable prefix, and GSP turned each cipher into a one-glance identification. Predictable headers inside obfuscated data do the same thing for attackers.
- Start with the most constrained evidence. The smallest segments forced every solve. Incident response works the same way: begin with the facts that leave the fewest possibilities.
- Most of the trace is noise. Eighty-one fragments, nine that mattered, all dressed up as ports and protocols. That's alert triage in one sheet of paper.
- Recheck your work. The 404 was a full on typo, ended with the flag still but delays for sure.
The theme across all of it: good fundamentals beat clever guessing, on paper and on the network.
Flag Scoreboard
| Incident | Hosts | Token | Flag |
| 01. Trace the Beacon | E1 · B2 · G3 · C4 · I5 · A6 · H7 · F8 · D9 | 79jc29m43awz | GSP{TRACE_THE_BEACON} |
| 02. Hack the Planet | E1 · I2 · A3 · F4 · D5 · G6 · C7 · H8 · B9 | n7n3wwn8mydx | GSP{HACK_THE_PLANET} |
| 03. Sorry, Dave | B1 · I2 · E3 · A4 · H5 · C6 · F7 · D8 · G9 | 86rd9ke822dw | GSP{I'm sorry, Dave. I'm afraid I can't do that.} |
That is the full flag set for Operation Ghostwire, start to finish.
Appendix: Commands and Tools
For the readers who want the how. The toolkit was tiny: a pencil for the grids, and either a shell or a few lines of Python for the decodes.
Incident 01, Base64.
echo 'R1NQe1RSQUNFX1RIRV9CRUFDT059' | base64 -d
# GSP{TRACE_THE_BEACON}
Incident 02, ROT13. tr rotates every letter 13 places.
echo 'TFC{UNPX_GUR_CYNARG}' | tr 'A-Za-z' 'N-ZA-Mn-za-m'
# GSP{HACK_THE_PLANET}
Incident 03, Caesar shift 3. Same idea, shifting back 3 instead of 13.
echo "JVS{L'p vruub, Gdyh. L'p diudlg L fdq'w gr wkdw.}" | tr 'A-Za-z' 'X-ZA-Wx-za-w'
# GSP{I'm sorry, Dave. I'm afraid I can't do that.}
The Python version, for anyone who'd rather not remember tr ranges.
import base64, codecs
print(base64.b64decode("R1NQe1RSQUNFX1RIRV9CRUFDT059").decode())
print(codecs.decode("TFC{UNPX_GUR_CYNARG}", "rot13"))
def caesar(s, shift):
out = []
for c in s:
if c.isalpha():
base = 65 if c.isupper() else 97
out.append(chr((ord(c) - base - shift) % 26 + base))
else:
out.append(c)
return "".join(out)
print(caesar("JVS{L'p vruub, Gdyh. L'p diudlg L fdq'w gr wkdw.}", 3))