rwthctf 2013 - Bank

The service bank consisted of a binary and a redis key/value storage database.

After netcatting to the service, which was hostet on port 3270, we found out that there is an Admin account, which presumably contained our keys.

First, we tried to reverse engineer the binary but found out that this was clearly not intended:

asprintf(&ptr, "%s%s", a1, "please_dont_try_to_reverse_this_this_is_just_anti_reimplementation_stuff");

Clearly, this was a dead end so we focused on the redis server. The server configuration was rather standard, so we applied basic security measures and renamed potentially evil functions like EVAL, EVALSA and others.

Trying to figure out what was going on we opened up redis-cli and specified the unix domain socket wich was used for communication between the service and the database.

redis-cli -s /home/bank/tmp/redis.sock

Using the MONITOR debug command we quickly found out a few things:

  • Each account has got (at least) 4 Keys:
    • USERNAME_log
    • USERNAME_registered
    • USERNAME_password
    • USERNAME_balance
  • The passwords were store in clear text
  • The password of the Admin-account was qwertys – it was the same on all hosts.

Each time the scorebot connected to our service it did three things. First, it registered two accounts. The passwords and usernames seemed to be generated randomly; although I personally assume they are not. These users transferred 1 shitcoin each to the admin and back, resulting in two log entries. These log entries were accessible through the CLI. The newest log entry was the first one on page on as these entries were prepended to the transaction list.

At this time we wrote a small script that went through all hosts and tried to log on with the standard password. I wrote a script that reset our admin password every minute to prevent it from getting pwned. Much to our surprise this worked relatively well.

Few hours later the situation changed dramatically. Most hosts did figure out the standard admin password by now and changed it so we had to redirect our efforts and went back to reversing the service.

We discovered hidden commands, namely DBG and ADMIN_BACKDOOR. Although we did not figure out the right command to use admin_backdoor, we could use the debug function, which was extremely powerful. Basically, it allowed us to read memory, four bytes at a time.

Now, we could read the passwords of the other teams following an easy pattern:

  1. Login with username “Admin” and some random password. It does not matter.
  2. The service loaded the correct password from the database. You can now read it with the debug function and get the ASCII clear text password back.

With this password we could log on into Admin again and steal more flags. Yay!

But how to defend against this attack? Resetting the admin password more frequently and using a longer password was only one part of the story. To do this, we used this little script which ran every minute:

#!/bin/zsh
COMMAND="SET Admin_password ";
echo "$COMMAND $(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)" | redis-cli -s /home/bank/tmp/redis.sock -x --csv > status.txt 2> error.txt

But this still did not stop the attackers from stealing our flags. But then we stumbled upon something interesting: The game server never checked if the flag is really in the Admin_log list. So we were able to safely delete them.

while ! test -f /tmp/stop-empty-admin ; do ( echo “LPOP Admin_log” | redis-cli -s /home/bank/tmp/redis.sock -x –csv >> our_flags.txt 2> error_flags.txt ) & sleep 5 ; done

This little sweetie here removes every five seconds the top entry of the Admin_log list. If the list is empty, nothing happened, and there was no error. Perfect to loop it!

But we were still not finished. Somehow, someone still managed to get hold of one flag per round. We did not figure out until the end what he was exactly doing. Looking at the action log I suspect that they somehow figured out the way the scorebot calculated username and password and logged on with the scorebot credentials. I tried to stop this with wiping the entire database log files, but this resulted in our service marked as broken. I tried to run the script every 30 seconds. Maybe it was only a timing thing which I could not get right, but I did not further investigate it as we could not submit flags while our service was broken.