SpeedHacking CTF at Hacktivity 2023 – Behind the Scenes
Introduction
At this year’s Hacktivity conference we organized the SpeedHacking CTF competition. Now it’s time to tell you how the idea came together, what we did and why we did things the way we did, the technical challenges we had with the CTF itself, and, of course, the solutions to the challenges.
How It Started
At the beginning of 2023, when the AI-hype train was already going at hyperspeed, I looked into how AI tools like OpenAI’s ChatGPT and GitHub’s Copilot could help me code. As I did not have a programming project at the time, I decided to write the Snake game. I had never coded a game before, so this seemed like a challenging (but reachable) goal. At that point, I didn’t have the CTF or any other goal in mind.
I subscribed to both ChatGPT and Copilot and started coding in VScode. I chose Python because that was the language I had practiced recently.
The beginning with ChatGPT 4.0 was amazing. The skeleton game it created was awesome. ChatGPT introduced me to excellent Python modules like pygame
. But, as soon as I started adding more ideas and functions to the game, ChatGPT fell apart. It started hallucinating variables, objects, functions, and methods that were never defined.
It was time to switch to Copilot.
And Copilot delivered. This made sense, as ChatGPT was never designed for programming. It is just a happy accident that it can help you code. Since Copilot is fine-tuned for just that, it does the job properly. I sometimes still found myself using ChatGPT rather than Copilot, but it was mostly for asking about problems that were not connected to the current code base.
And while I was coding Snake, I started thinking about hacking it or even intentionally putting vulnerabilities into the game. Around this time, I found the amazing pygbag
Python module. With just a little bit of code modification, I could host the game on the web and run games in a browser. You can find some details on how this is done, but the two main keywords here are WebAssembly and CPython.
And this is how the idea for the CTF was born.
Back in 2019, I had organized a SpeedHacking CTF competition at Hacktivity, so I thought it would be nice to do a comeback this year and organize a CTF with these challenges. In my vocabulary, speed hacking is about two people (or teams) competing, where their screens are shared with the audience, and, to make things even more exciting, there are two “sports commentators” to tell everyone what’s going on. Fortunately, we had the privilege of current and former ZDI employees entertaining the audience during this year’s competition.
The Qualification Round
Since we needed to get two capable hackers for the finals, I also had to arrange a qualification round. What made things challenging was that the quals were hosted online, while the finals were hosted locally on my MacBook at the event. This meant that I had to set up two CTF infrastructures 🙂 I expected both the qualification round and the finals to be solvable in ~90 minutes and I am happy that my estimates were correct.
To solve the quals, you had to perform the following steps:
First, you had to find the QR code at the event. This redirected you to a YouTube rickroll video. If you paid attention, you could see that the QR code pointed to the actual CTF web server, which then redirected you to the video.
The root webpage had all the rules and a decoy challenge. It’s only purpose was to provide hints for finding the real challenge. The real challenge was hidden in the good old robots.txt
. In the beginning, Cloudflare caches played a trick on us but, luckily, we resolved the issue.
Once you had the URL and used the special user-agent, you needed to buy a ticket for a different price, which was set by the server. This beginner challenge could be solved by modifying the page in the browser’s developer tools or changing the browser request in an intercepting proxy.
The next challenge was bypassing a custom authorization page. It was a bit tricky and not obvious even from reading the source code, but the solution was to change the order of the GET
parameters.
The next challenge involved a hidden login page with obfuscated JavaScript. One solution was to check the JS code and find that the key parts were not obfuscated. By creating proper local storage keys and modifying JS variables, it was possible to enable the login form. The other solution was to find out where the form was being decoded and simply dump the decoded format into the DOM via document.write
or a similar method.
As we were looking at the server logs in real-time, we sometimes let the participants know whether they were on the right track on Slack. I have no idea how they could check Slack while hacking, but it looks like they are better at multitasking than I am.
The final challenge was to read the contents of the file called flag
by using the login form. Clearly, this was an injection challenge, and we provided hints that the challenge uses Jinja. There also was another hint about a flash.
The solution was to use Server Side Template Injection (SSTI), but, for security reasons, only a few functions were allowed. Checking our favorite payload repository, it was easy to figure out that we must have used the function get_flashed_messages
.
And this was the end of the quals! The flag contained a URL to a Google Forms form. What made things exciting was that the first three people finished solving the challenges in a 5-minute window. After announcing the winners, the second place withdrew from the finals.
The Finals
As I’ve mentioned, we ran the finals at the conference. I am always afraid of CTF dependencies that I don’t control, so I put a significant effort into making everything available from the local network. Unfortunately, modern OSes and browsers can trick you.
The CTF setup used a valid Internet domain (speedhack.xyz), but it pointed to a local IP address (192.168.x.x). When the Internet is not available, modern browsers don’t even try to connect to the local IP, even if they can resolve the domain via a local DNS server. Through many hours of frustration, I learned about scutil --dns
(thanks, GHost), that nslookup
and netcat
use different subsystems to resolve names, that I could put domains into the /etc/resolver
directory, and about complex projects like https://github.com/Jamesits/alwaysonline.
But let’s get back to the finals. The entry point to the finals was a Workadventure level with a custom Christmas map.
What makes Workadventure amazing is that you can self-host it for free. But it is also a complex beast, as it starts with 13 containers. Fortunately, it wasn’t hard to start and leave it running.
My initial idea was to deeply integrate Workadventure into the CTF platform. Unfortunately, this plan failed due to the complexities in the maps, incompatibilities in the maps and editors, and my lack in design skills. In the end, the Workadventure platform was used only for providing hints, finding the entry URL to the real CTF platform, and hosting the first challenge.
The first challenge had hints that were provided randomly from a pool of answers, but if you checked the source code, you could find a hint that was impossible to get. From there, the challenges involved only the Python Snake game.
The goal of the first challenge was to reach a score higher than 4,294,967,000. The game had three types of food for the snake: one normal and two special ones. The first special food added bonus points, increased the snake’s speed and added new walls. The second special decreased the total points, decreased the snake’s speed, and removed some walls. The solution was to eat the second special food and cause an integer underflow in the game.
The goal of the second challenge was to reach a high score of 1337. This was impossible by default, as the points were increased or decreased by 10. The solution was to use string injection in the high score page. As the game stored the high scores in a “nickname,score
” format, one could trick the parser by submitting a nickname like CoolJoe,1337
.
The goal of the third challenge was to re-enable the Python debug interface. By default, pygbag
provides a debug interface to the games with a full Python console in the browser. I removed this functionality by commenting out some lines in the JavaScript code. The solution was to uncomment those lines of code. This can be a little tricky, as you have to modify the response from the webserver. Modifying it locally in the browser does not do the trick.
The fourth challenge was to teleport the Snake into a box. Once you had access to the Python debug console, you could change anything you wanted, including the snake’s coordinates.
The final challenge was about getting the source code of the Snake game, reverse engineering it, figuring out how the communication is encrypted between the browser and the server, finding the encryption algorithm and keys, then forging a valid message “Hack The Planet
” and sending it to the server.
During the game, Özgün was always ahead of Balázs by 2-5 minutes. But Balázs took the lead in the final round and finished the CTF right before Özgün!
It was a very challenging game in a very exotic platform and both hackers deserved the prize, but there could be only one winner.
I would like to thank Marton Bak for helping brainstorm and test the challenges, Veres-Szentkirályi András from Hackerspace Budapest for video mixing, the commentators from ZDI, and the organizers of Hacktivity for the amazing event.