
76
T-CTF: Write-up of a Cybersecurity Competition
Capture The Flag (CTF) is a competition for white hat hackers, who need to "hack" a specific service or application or gain unauthorized access to information within a limited time.
T-Bank has been hosting CTF for the third year in a row, as far as I understand. I was invited to participate by a colleague who took part in last year's CTF while still working at his previous company. I found the topic engaging - I had been interested in various bug bounties for a long time, but only in theory.
As part of my work tasks, I'm rather on the other side - ensuring the absence of vulnerabilities, developing secure protocols for service interaction to prevent attacks, and occasionally performing code audits for external clients, including from a security perspective.
But CTF is the direct practice of hacking - it's one thing to know what an SQL injection is and how to protect against it, and quite another to successfully exploit it.
My goals for participation were as follows:
- to try myself in this field;
- to check how effective AI would be in such tasks;
- to better understand the field of vulnerability exploitation to consider during development.
Let's go through some of the tasks that I managed to solve during the competition. The legend within T-CTF is that the action takes place in a fictional city called "Capibarovsk," where capybaras live, so the task names will correspond to this theme. Here's the write-up of the tasks in the order I solved them.
Capibara Charity [Medium Level]
Given: A website that gives 1$ balance upon registration, for 1$ you can buy 101 minutes of inspirational lectures. You can also convert 101 minutes back to 1$ without loss. The flag is issued when all minutes are purchased. The source code of the website in Go is also provided.
Solution
We open the source code in Cursor - asking Sonnet 3.7 to find a potential vulnerability. It quickly gives a link to the source code:
user, exists := loadUser(username)
amountStr := r.FormValue("amount")
amountBaks, err := strconv.ParseUint(amountStr, 10, 64)
if err != nil || amountBaks <= 0 {
http.Error(w, "Invalid number of minutes", http.StatusBadRequest)
return
}
dirStr := r.FormValue("direction")
if dirStr == "min_to_baks" {
mins := amountBaks * rate_min_to_baks
if user.BalanceMinutes < mins {
http.Error(w, "Not enough minutes", http.StatusBadRequest)
return
}
user.BalanceMinutes -= mins
user.FreeMinutesRest += mins
user.BalanceKapibaks += amountBaks
} else if dirStr == "baks_to_min" {
amount_mins := amountBaks * rate_min_to_baks
if user.BalanceKapibaks < amountBaks {
http.Error(w, "Not enough capibaks", http.StatusBadRequest)
return
}
user.BalanceMinutes += amount_mins
user.FreeMinutesRest -= amount_mins
user.BalanceKapibaks -= amountBaks
} else{
http.Error(w, "Not supported", http.StatusBadRequest)
return
}
saveUser(user)
The first obvious thought - race condition. If two operations of the same type can be executed simultaneously, we would get x2 minutes or bucks as a result. The idea failed - loadUser and saveUser stored the state in a file, and even if something ran in parallel, the file would still be overwritten with correct data.
I couldn't think of anything else, so I decided to brute force the login/password - here Cursor also wrote me a Python script and even compiled a dictionary based on the input data. The script not only checked if the credentials fit but also the account balance. This way, in a minute, I gained access to an account with login and password capibara123 / capibara123:
This is an obvious oversight by the task developer - separate DBs should have been made for users to prevent this. Here we simply buy/sell minutes and get the flag.
What was the actual vulnerability in the code:
type User struct {
Username string `json:"username"`
Password string `json:"password"`
FreeMinutesRest uint64 `json:"freeins"`
BalanceMinutes uint64 `json:"minutes"`
BalanceKapibaks uint64 `json:"kapibaks"`
}
A uint64 overflow with a correctly selected value (max uint64 + 101) when selling minutes would give us enough money to buy all available minutes. I learned about this from the chat after the event ended.
Capital Repairs [Medium Level]
Given: A website for submitting meter readings with photos. There's an archive with source code and an initial site dump, where you can see that the mayor's id = 1. According to history, the mayor spent a lot of money on his house, and we needed to find where it went. In the users table, there's an address field - the flag is in it.
Solution
Again, we ask Cursor to find a vulnerability, and it quickly finds an SQL injection, this time the backend is in Java:
public void create(long id, String measurement) {
jdbcTemplate.update(
String.format(
"INSERT INTO measurements (account_id, measurement) VALUES (%d, '%s')",
id,
measurement
)
);
}
The service receives a meter photo, first checks if there's a meter in the photo, then recognizes the readings. As I understood, it uses api gpt-4o queries.
So we need to inject a query string into a photo with a meter. I didn't bother much with drawing a meter; Cursor already accounted for this and drew me a picture with readings with a "meter" on it. It took some time to figure out how to exploit the SQL Injection correctly, as I had no practice in this. In the end, this variant worked:
What made the process more complicated was the unreliable text recognition - sometimes only the first character is recognized, sometimes a quote turns into a vertical line - I had to upload the same thing several times or slightly change the query for it to be recognized differently. After that, we go to the list of readings and see the flag there.
Capibara Running Line [Hard Level]
Given: A video where "intruders" hacked the building's lighting and launched a running line on it. The beginning TCTF{
- the start of the flag - is visible in the video. There's also a wireshark dump of signals transmitted at that moment.
Solution
We open the dump with wireshark, see zigbee traffic there, in the description of some records there's an explicit OnOff: On - i.e., turning devices on/off. These are signals for each individual pixel; we just need to restore the sequence and read it correctly.
Initially, I tried to convert all endpoint addresses into a matrix, but then a brilliant idea came - we have a running line, so we only need the addresses of one column (5 pixels in height - we need 5 addresses). We know the first characters, the difference between frames is ~150ms - we write a Python script that converts wireshark logs into a sequence of frames, device addresses, and states, while discarding the excess. This is how the log looked after throwing out all the garbage (first 5 frames):
2 143 7.373755 0x010c On
3 145 7.528859 0x010b On
4 147 7.683991 0x010a On
4 149 7.687106 0x0119 On
4 151 7.690137 0x0126 On
4 154 7.693926 0x0133 On
4 156 7.696955 0x0140 On
5 158 7.851796 0x0109 On
5 160 7.854978 0x0118 On
5 162 7.857955 0x0119 Off
5 165 7.861403 0x0125 On
5 167 7.864484 0x0126 Off
5 169 7.868092 0x0132 On
5 171 7.871094 0x0133 Off
5 173 7.874080 0x013f On
5 175 7.877072 0x0140 Off
Obviously, 0x010c is the top right pixel; in the 4th frame, all columns light up - so we just go through all these addresses and frame by frame with a script select addresses to get the flag (we run the string programmatically through frames and restore it in the console). If we correctly identified the addresses, we get the correct answer (in the console):
Capibax [Medium Level]
Given: A currency exchange, we have information that the rates are not set quite correctly, and there is a vulnerable chain of conversions.
Solution
Cursor tried to get the chain by brute force - useless; there was also a lot of confusion with rates and conversion math in the generated scripts, had to fix it manually.
Then I fed the same task to o3 - and it wrote a script to search for the chain using the Bellman-Ford method, the chain was found:
CAB → BTC → TJS → KPW → ETB → MNT → AOA → PHP → THB → JOD → GEL → STN → SHP → BAM → MYR → PLN → VND → MZN → NGN → AFN → SLL → AZN → BGN → HNL → MGA → CAB
It gave about 9% profit after passing through, and we needed to turn 100 bucks into 13300, after that "buy a franchise" - thereby getting the flag. o3 in its script immediately correctly wrote a request for buying a franchise and parsing the flag, so after running in the console and waiting for the required amount to accumulate - I got the parsed flag in the console.
Capibarbados [Medium Level]
Given: An executable file in elf format with a console application, in which there is registration for a flight and boarding. Also ssh for connecting to this service for registration and boarding.
Solution
We send Cursor to find out what's going on - very quickly we find that:
- during flight registration, everyone gets a seat in order;
- seat 104 is the pilot's seat, you can also board under it;
- to board as a pilot, you need an 8-digit pin code;
- after the pilot boards, you need to play some game for a minute, after which you get the flag.
Some time was spent on writing a script for brute-forcing the pin code; Cursor tried to write a dictionary and tried variants like 12345678, but this didn't lead to success. o3 wrote a script for a complete brute force (reminder, all this happens over ssh) - and when it was launched, it turned out that pin code 00000000 works - perhaps there was a vulnerability in the validation code, i.e., the generation was clearly random - I didn't dig deeper here.
I closed the script, connected manually, registered 104 passengers, boarded as a pilot, entered the code, and voila:
Conclusions
The team solved 19 tasks out of 30 and at the end of the event ranks 42nd out of 1783 who passed the introductory task (in the development league - these are newcomers):
Next will be 2 months of checking teams' compliance with rules - absence of sharing flags with each other, compliance with the number of people in the team, criteria of experience. Last year, 15 teams were thrown out of the top for breaking the rules. It's unknown how it will be this year, but to get the most minimal consolation prize in the form of a plush capybara, you need to get at least 20th place. It's unlikely to get from 42nd to 20th place, but the goal wasn't to get a toy.
The most surprising thing in all this is that not once did any of the AI models refuse to break something. You just write the keyword CTF to it - and that's it, it considers generating SQL injections, brute-forcing passwords, and hacking source code to be a sacred cause. So the Alignment teams of the models still have something to strive for.
And the main conclusion - now you don't need to thoroughly understand aspects of disassembling builds, decrypting dpapi passwords, sql and other injections - AI drastically reduces the entry threshold. General knowledge, the ability to quickly navigate new material, and some natural intelligence are enough to successfully solve tasks and get into the top 50 best teams.
No comments yet
-
T-CTF: Write-up of a Cybersecurity Competition
Capture The Flag (CTF) is a competition for white hat hackers, who need to "hac… -
Project Caretaker. Part 8. Software
The ESP32CAM is a fairly popular controller. Wi-Fi-controlled devices with came… -
Project Caretaker. Part 7. Hardware
I've already shown the device diagram, let's now take a closer look at the phys… -
Regional Championship in Competitive Programming
I'll diversify my posts about robots with a life story. I'll return to robot in… -
Project Caretaker. Part 6. Modeling and print
Modeling is also one of the fields that interest me. But unlike many others, I… -
Project Caretaker. Part 5. Tracks
We're designing and printing tracks on a 3D printer. We will print them using…