CISCO SECCON AD-CTF 2020

Rohan Mukherjee
csictf
Published in
17 min readOct 13, 2020

An Attack Defense CTF organized by CISCO and team bi0s.

CISCO SecCon 2020

SECCON Attack Defense CTF was a 12-hour CTF held on October 11th, 2020. Since this was our first Attack Defense CTF, we spent the night before searching for tools and planning out our strategy for the same.

Pre-CTF Planning

In an attack defense CTF, it’s always a good idea to make scripts for your exploits, since you’d have to re-run them for every tick. So we tried to automate our exploits as far as possible.

Before the CTF started, we had to plan 3 main things:

  • A way to monitor network traffic on our VM so that we can find out when someone is stealing our flags.
  • Utilities that can be reused, such as a function to identify flags and a function to submit flags.
  • A generic script to run all our exploits for every tick.

Network Monitoring

For monitoring the network, we used a tool called ngrep.

ngrep is like GNU grep applied to the network layer. It’s a PCAP-based tool that allows you to specify an extended regular or hexadecimal expression to match against data payloads of packets.

Utilities

Extracting flags from the text could be done easily using a regex, which was provided in the rules.

The submit_flag function was created to accept a flag / a list of flags and submit them through sockets to the flag server. We used the remote function from the pwntools python library to connect to the socket and send the flag(s) to the game server.

Reusable utilities

Exploit Runner

Initially, we planned that all our exploit scripts would have some sort of functionality to re-run themselves once in every tick, but that would be a repetition of code. So, we wrote a Python script to run all our exploit scripts once every minute.

This script was designed to take the name of the folder containing the exploit scripts as an argument, and run each exploit script inside the folder once every tick. So, if you had a folder called final_exploits containing all your exploit scripts (in python, of course), you could just do:

python3 repeat_submit.py final_exploits/

Now, you could just keep adding your exploit scripts in the final_exploits directory without worrying to have to execute them every time. Since our exploits used the utilities.py script too, we had to place it in this directory and add it to the ignored_files array.

We later realized that we could improve this script. In the current version, it would wait for an exploit to finish before running another, but it would be much more efficient if we could run them in parallel. However, we went with this script since the tick interval was 5 minutes, which was sufficient to run both the exploits.

Initial Setup

We were provided with an ssh private key by the admins to login inside our team’s VM instance.

ssh -i key.pem root@VM_IP
Logging into the team VM

Since we had a team of 3, it was really important to set up tools so that we can properly collaborate and work together on the server. Our first step was to install tmux, a terminal multiplexer, which lets you manage multiple terminal screens, and also collaborate with others on the same terminal session

sudo apt install tmux

And then one of us started a tmux session by running

tmux

While the others joined the same session by running

tmux attach-session
A Tmux session with all team members connected

Then we proceeded to install all the tools we mentioned in the Pre-CTF Planning section:

sudo apt install ngreppip3 install pwntools

We set up a tmux window with two panes, one for running ngrep to monitor log all network packets with strings matching the flag format regex bi0s\{\w{26}\} to a file, and another to show live trailing logs from that file

sudo ngrep -d any -q -l ‘bi0s{.*}’ > /root/capturetail -f /root/capture
Logging packets using ngrep

Whenever a network packet with a flag would flow from/to our VM, it would show up as on this terminal pane, which was highly useful during the CTF to let us know if we were losing flags through a service.

For example, here is an instance of us losing flags through an HTTP service:

F, we just lost a flag in an HTTP response

In some cases, we would also get enough information to know about the kind of exploit the other team’s used to steal the flags, which was extra helpful to patch vulnerabilities.

We initially planned to move our exploit script utilities (from the previous section) on the same VM but were paranoid that a team would be able to steal our scripts if they somehow managed to get RCE on our server. So we spun up another GCP instance to act as a ‘workstation server’ on our own account and set up all our exploit scripts there. For most of the initial half of the CTF, this is where we ran/tested our exploit scripts.

The admins had provided us with an OpenVPN config file to connect our own machines to the game server network, so we used this file to connect both our local machines, and the new VM instance(our workstation) to the game server network:

openvpn --config client.conf

This lets us refer to machines on the game server using their internal subnet IP addresses, as we were in the same VPN as the game server:
1. Gameserver (where flags were to be submitted) @ (10.40.0.2)

2. Team VMs @ (10.42.0.x) (where x ranged from 0–70, one address for each team’s VM)

Finally, we got to actually starting to look around the VM to see how the vulnerable services were set up.

We noticed that there were two services, /opt/postoffice, and /opt/masalaD. We ran

sudo systemctl status

and observed that both these services were running using systemctl unit files:

We noticed two systemd services, masalaD, and postoffice

The CTF Begins!

The Discord server when the CTF began.

When the CTF started, participants were given a JSON with team names and IPs. Initially, we started looking at the masalaD service (since it was in Python), and we noticed MySQL. Instantly, we suspected that this would be vulnerable to SQL Injection, which it was.

Patching masalaD

In some places, the SQL query was properly implemented, such as

cursor.execute('SELECT * FROM user_detail WHERE email = %s AND password = %s', ([Email], [password]))

However, in some other places, such as in the /show_cart route, it was implemented using format strings, which made the route vulnerable.

cursor.execute("SELECT email,product,total_price,note FROM cart WHERE email='%s' AND name='%s'" % (email,name['user_name']))

So, we patched all such occurrences of format strings in SQL queries to something like:

cursor.execute("SELECT email,product,total_price,note FROM cart WHERE email=%s AND name=%s", ([email], [name['user_name']]))

We had patched all of the SQL vulnerabilities within the first 5 minutes of the CTF, but after we restarted the service using systemctl restart masalaD.service , we didn’t really care to check if our service was still up on the scoreboard. After some time, when we checked the scoreboard, we found out that our service was down, and we lost about 25% SLA right there. In one place, we forgot to remove the quotes around %s in the query, which caused the server to crash. We fixed that as soon as we could, and then started working on exploiting this issue to steal flags from other teams.

Exploiting masalaD!

masalaD landing page.

By now, we know that the /show_cart route was vulnerable to SQL injection, so we started looking into how we could exploit it.

We noticed that this route takes the email and username from the current session and uses this to query the email, product, total_price, and note columns from the cart table. On our VM, we logged into MySQL and saw that the note in the cart table had the flags in it.

Now, if our email was something like:

' or 1=1 -- 

The query would become:

SELECT email, ... FROM cart where email='' or 1=1 -- AND ...

This would match all rows in the table, and leak all the flags! But for this, you would have to sign up with your email as ' or 1=1 -- for all the teams. The same would also work for the user_name field in the session, which is what we used finally. So, instead of doing this manually, of course, we wrote a script.

PoC masalaD — Signing up with name ‘ or 1=1 —

By this point, some teams had figured out the issue in the postoffice service and had started stealing our flags, so a part of our team started looking at issues in the main.go file to hunt for vulnerabilities.

Coming back to the masalaD service, we used the requests module in Python to execute some functions in the following order:

  • Sign up
  • Login
  • Visit /show_cart and extract flags.

Here’s our script:

The functions in the script are pretty self-explanatory, a session s is created to cookies returned from the server. Using that session, POST requests are made to /signup, /login, and /show_cart, respectively. The get_show_cart() function returns the response, which is an HTML file containing the data we leaked from the database. You would notice that the name used to sign up is "' or 1=1 -- " which is actually our SQL injection payload.

Once we get a response from /show_cart, we extracted the flags using extract_flag utility we referred to in the Pre-CTF Planning section. Finally, we submitted the extracted flags using the submit_flag utility.

We looped through all the teams in the JSON we received at the start of the CTF and carried out the exploit on them. Finally, we placed this script inside the final_exploits/ directory on the server, so that it keeps running for every tick.

An initial look at postoffice

The post office service was written in Go. An initial look at the main() function in main.go, showed us that the service was running on port 8080:

Also, no mention of go’s HTTP module in the source told us that this wasn’t a web service, but just a simple TCP service listening on the port. Our first plan of action after this was to use the service and try to identify where the game server was dropping the flags.

We noticed a messages folder inside the post office service directory and saw files with random characters being created inside that, and these had the flags!

The gameserver put the flags inside the messages folder

So the goal was to steal these flags from the messages folder from other teams, and also patch our own service to prevent teams from reading files in this folder.

Our first approach to understanding how the service works were to actually use it, so we ran

nc localhost 8080

and tried to use the service. We noticed the following things:

  1. The service had a login and register feature, to maintain user accounts
  2. Once you were logged in, the service either let you read a given filename from the messages folder or write a “letter” which would create a new file in the messages folder (with a seemingly randomly generated name). There was also an option to list all files in the messages folder, but this was only allowed for users which were “admins”
  3. We noticed that registering with an email which has “admin” in it wasn’t allowed by the service

At this point, we knew the goal was to clear, we had to somehow figure out the “random” file names in the messages folder, and then we would be able to steal flags by reading letters through the service!

We noticed that the filename was generated by calculating MD5(to the field of letter + current timestamp) :

Sadly, we hit a dead end here, we tried to look at the whole source but didn’t really figure out a way to calculate these filenames/steal them.

We already knew we were losing flags through this service, so we followed a different approach:

  1. We added logging statements (log.Print() ) on each function in the Go source code, to monitor better how other teams were exploiting the service
  2. We tried to look at the ngrep logs setup before to figure out how teams were stealing flags

We quickly patched the binaries with logging statements and rebuilt the go binary:

go buildsystemctl restart postoffice

and before we knew it, bingo, we checked systemctl logs postoffice and saw that when someone did a login attempt using the email admin@gmail.com, even though the user wasn’t found in the service, somehow, code execution was being routed to the GetAllToken function which caused all the filenames in the messages folder to be printed!

We tried the exploit on our service locally, and yes, it did work:

Stealing flags from postoffice

We went back to the go source code, and now we're able to easily split the vulnerable section of code:

After logging in, the service seems to check if the login succeeded with resp[“status”] == true in an if condition, but right after that, there is a piece of code that checks if the email string contains “admin”, and then it calls GetAllToken!

The fix was simple, we just commented out the if the condition on lines (57–61), and rebuilt the binary, and restarted the service, and we didn’t lose any flags through that service till the end of the CTF :)

Now that we patched our binary, it was time to write an exploit to steal flags using the same vulnerability from other teams:

The script is pretty self-explanatory, but it does the following sequence of actions

  1. First, it registers with a random (non-admin) user account in the service and sets a random email and password
  2. Then it logs in using the email (admin@gmail.com) and password (adminpassword) (the password doesn’t really matter) and then extracts out all the filenames printed out (due to the vulnerability :) )
  3. Then, for each filename in step 2, we login as the user created in step 1, and retrieve the letter with that filename, thus stealing the flag!

Optimization problems

We ran both our exploits and started looking for more vulnerabilities, but soon we noticed that the postoffice exploits other teams were using were much faster as compared to ours. We were pretty sure that our exploit was the same, so we tried to optimize the script by making it multi-threaded.

We tried 2 major optimizations, the first of which was to run the exploit for multiple teams simultaneously. We used the threading module in Python for the same. Another improvement we made to our script was to add a dictionary which stored the tokens that were visited for each team so that the script does not waste time getting flags from the same tokens again.

The optimizations were made in about 30 minutes, but we spent a huge part of the CTF fixing bugs in the script. After a few hours of debugging and fixing silly mistakes, the script was running fine and was almost as fast as the other teams.

After spending a lot of time on optimization, one of our team members noticed that the team server provided by the organizers had 16 vCPUs, whereas we were running it on our own 2 vCPU server. This is when it struck us why the other teams’ exploits were so fast. We shifted our scripts to this server and they were now insanely fast (since all the machines were now on the same subnet too). Finally, we could start looking into other vulnerabilities to exploit.

Discovering new vulnerabilities

In the masalaD service, we noticed a vulnerability that was very sneakily placed, which allowed you to log in as admin.

Instead of checking if the Email was exactly admin@masaladosa.com, the script was checking whether admin@masaladosa.com was in the email. If this check passed, the user would be identified as an admin. So, we could easily signup as newadmin@masaladosa.com and become admin, since admin@masaladosa.com is a substring of our email!

Now, once you’re admin, you get access to the /admin_secrets route.

In this route, we can again leak the cart if admin is present in secret_dec. Now, secret_dec is the result of some crypto function, which is present in AES.py.

We tried a couple of things to exploit this issue, especially since we knew the key and the IV, but we couldn’t come up with something that would work always. So we decided to see if there were more issues in the postoffice service.

In main.go, we noticed the following code segment.

Notice how the file is accessed using mydir + "/messages/" + FileName. The FileName is something we control, so we can make it something like ././../../../../../../etc/passwd (exactly 32 characters in length as required by the function), we could include any file from the server since the path would become mydir + "/messages/" + "././../../../../../../etc/passwd" , which is eventually /etc/passwd. First, we patched this issue by checking for .. and . in the FileName. If the filename had . or .. in it, we’d just return and possibly throw an error.

Now, to exploit this, we tried leaking the app.secret_key from masalaD through postoffice. So, in TrackLetter, we had to pass the following payload:

../../.././../opt/masalaD/app.py
Leak masalaD source through postoffice!

This would leak the entire app.py file, which had the changed app.secret_keys for the team! With this, we could basically log in as any user, since we can predict the cookies that flask would generate. We could also leak keys from AES.py. From here, we’d again have to figure out how being admin could help us, so we tried something else instead.

We saw that the app was running in debug mode for most teams. So if you can manage to raise an error (for example, an SQL syntax error in the username).

Flask error.

An interesting thing about this is that it lets you execute commands if you have the right pin.

Flask asking you to enter the pin.

Once you enter the pin, you get a python shell, through which you can execute code on the server. This would be really dangerous since we could simply log in to any teams’ server and steal flags (or remove services, run rm -rf /, you get it).

Remote code execution through Flask debug console.

Flask generates a random pin every time the server is started and logs it on the console. We could try to get the pin from the logs! The only thing we need for this is the system log file, which is stored in /var/log/syslog.

/var/log/syslog

On our own VM, we saw that the debugger pin was indeed present in /var/log/syslog. We were really excited because if this works, we would have control over all the VMs basically. So, we tried the following payload:

./././../../../../var/log/syslog

Sadly, this failed :(, since /var/log/syslog was owned by root, and postoffice was run by the user postoffice. It was worth a try though, it would’ve been amazing if this worked.

Finding a vulnerability in postoffice by accident

It was at this point that we had about 3–4 hours left till the end of the CTF, and still had a wide gap between us (at 2nd place) and the team in the first place, so we were desperate to find any kind of new exploits. We were also refreshing the leader-board frequently, as 3rd place was (very) slowly closing up on the gap on us (maybe they had more efficient exploits ?).

In these frequent refreshes, we noticed something really odd in one of the game server ticks. The ‘post office’ server showed the status as “corrupt” for a single tick for the top 2 teams! The other teams probably ignored this, but we knew there was a teeny-tiny chance that somehow, someone, by mistake managed to crash our service by passing an input, causing our service to go down for a fraction of time.

We checked the logs and found that an out of bounds slice was causing go to panic and error out and crash:

Something was causing the postoffice service to crash

And we quickly found the code in the service that was causing this, by simply doing a search for the slice [:32]:

Well, this was simple to patch, we had to just add some length checking before doing the slice to see if the length was lesser than 32, but this is where brain cells kicked in. If this could crash our service, this could certainly be used to crash other’s services too :). This might seem evil, but we believe we had good enough reason to do this:

  1. This was certainly a bug and could be patched by teams
  2. The CTF was pretty stagnant for the past few hours, just the top few teams using the same scripts to exploit lower table teams which weren’t really fixing the services, this would certainly spice things up a bit xD

We quickly separated the work, one member of our team started patching our own service, while two others started cooking up a script to take down the post office service for other teams :)

The patch was simple, we just added the following lines in the TrackLetter function to check the length of the token before slicing it:

The exploit script was mostly copied from our script to steal flags from the service, but with small change

The script simply registers a random user, logs in as that user, and sends a request to track a token name of length < 32.

And yes, it worked! here is a picture of the leader-board at the tick we ran the script:

Hmm postoffice somehow crashed for everyone else, except us

Unfortunately, we were able to do this only for a few ticks, because the admins asked us to stop this as they saw this as DoS. Whether or not this should have been allowed is pretty debatable (from our perspective, this was a bug that could be patched, so it should have been allowed), but we had to stop our scripts, as the alternative was getting disqualified :).

Conclusion

Overall, this was a pretty fun experience, with a lot of twists, especially towards the end :). We placed 2nd in the CTF, and one thing we learned is being pre-prepared for an A/D CTF with tools can give you a considerable edge over other teams. Hope you found the article interesting, that’s it from us!

Links

Authors

--

--