Picure this: it’s Thursday evening and you’re scrolling through your Twitter feed. X-MAS 2020 - the CTF that your team organizes - is going to start in less than 24 hours. You see a retweet of an announcement from Pentester Academy: their weekly webapp ctf is going to start tommorow. To be more exact, it’ll start in 8 hours. You do what any other normal person would do and click ‘register’.
All jokes aside, the intended solution for this CTF was pretty interesting, so I decided to share it with you. I’ll also include a small section at the end about how I was able to finish the challenge in 3 hours, out of which I only worked 1 1/2. Let’s begin!
What is this? - Initial Enum
After starting the lab on AttackDefense, I was provided with a link that took me to a Kali machine which I could control from my browser (I must admit that I consider this ‘vm in your browser’ thing amazing). A browser tab was opened with the target site:
The frontend was built using Vue.js. I tried logging in with some incorrect data just to see where the request goes:
That IP address is the address of the target machine - this means that an API is exposed on port 8000.
Logging in - FLAG1 and FLAG2
The first thing that I did was to make the request with cURL - that way I could easily tamper with the request’s parameters. Here’s a summary of the process:
Long story short, the ‘email’ parameter was vulnerable to SQLi. The frontend alerted the user when the email address was not valid, but the backend did not have any checks in place. Appending ' OR 1=1 # to the end of my non-existend address allowed me to create a session, which looked like a base64-encoded string. I proceeded to try to decode the session id:
The 2nd flag can be clearly seen, but where was the first one? The response headers.
In case you’re wondering, the -i switch tells cURL to print the headers of the server’s response.
Running code - FLAG4
The first thing that I’d do would be to change the sessid token so that admin is set to 1 - admins often have access to more functionality than normal users. To do that, I picked the decoded JSON string, changed 0 to 1 and re-encoded it. I also modified the email so that I used the previously-found SQLi (not doing this last step result in an ‘invalid session’ error -> the SELECT statement that validates the user exists also checks wether admin is set to the right value).
I wanted to use the frontend, but I still didn’t have any valid creds. I inspected the app’s source by accessing all .js files and appending .map at the end. That way, I found out the session was stored inside the localStorage object, so I used the following command to trick the app that I was logged in:
To understand what I did, look at this screenshot from my browser’s console:
The app stores some variables as a base64-encoded string. You can see the encoded JSON as the output for the 2nd command. The 3rd command updates the app’s saved state, setting the seesion id to one that will get validated by the server. I also took the liberty of setting admin to true and changing the email/username, because only the session id is sent to the server (meaning that these changes are only reflected on the frontend). After refreshing the page, I got redirected to the IDE:
FLAG4 is visible on the page. FLAG3 can be found in the response headers if the server indentifies the token as belonging to an admin, but this flag can be read later.
Breaking in - FLAG5 and FLAG6
I had the ability to execute code on the target machine - the next logical step was to turn that to a reverse shell. I first ran a simple python script to see if I could execute system commands:
I used ShellGenerator to generate a command for my reverse shell. The payload you choose doesn’t matter much as long as it works - for example, I took the python payload and formatted it.
FLAG5 was an environment variable and FLAG6 could be found in the current user’s home directory:
Rootin’ it - FLAG3, FLAG7, FLAG8, FLAG10
After a bit of enumeration, I found the /cleanup.sh script:
Apart from containing FLAG8, this files seemed to be ran constantly, maybe by a cron job (look at the comments). Also, the ‘-user’ switch was provided to the ‘find’ program, meaning that a oser other than ‘codebot’ might be running the script. Thankfully, the file could be written by anyone:
I echoed a reverse shell payload in the cleanup.sh file, hoping that it would get executed by a user with elevated privileges. After a few seconds, the script got executed by the root user:
After getting root, it was time to get FLAG3 and FLAG7, which I somehow didn’t get during my exploitation process. FLAG3 was located in /root/pythonAPI/API.py (it got returned as a header if an user marked as ‘admin’ compiled any code) and FLAG7 could be found in /home/admin/FLAG7. Also, FLAG10 was located in a file named FLAG10 inside root’s home directory:
Finishing it off - FLAG9 and FLAG11
While inspecting the source code of the IDE, some credentials stood out:
I used those credentials to connect to the local MySQL server. Since my terminal was not interactive, I used the ‘-e’ switch to run one SQL query each program call:
The CTF page told me that FLAG11 is root’s database password, so I tried to get the password hashes for all MySQL users:
There were 2 passwords: one was michael’s and one was root’s. Since I already knew michael’s password, I put the two hashes into john and hoped that I will see another cracked password:
FLAG11 is ‘1234567890’.
Appendix 1 - How I got root in under 2h
Well, let’s look at the 404 page of the API server (port 8000):
I knew the web app was using Flask (try fuzzing the login endpoint by providing a number as email/password values), and a template seemed to be used when I accessed non-existent URLs (notice that the root page, /, returns another 404 error). This led me to believe that the 404 page was using a template that reflected user input (the non-existent path):
I still can’t believe that worked! The vulnerability is called a Server-Side Template Injection (SSTI) and often leads to RCE. Read more about this here. Long story short, I got RCE as root:
To get a reverse shell, I highly recommend encoding the payload in base64 and running echo [enc_payload] | base64 -d | bash on the server. After getting a reverse shell, it took me roughly 30 mins to gather all the flags and submit a short report.
As recent events show (cough FireEye cough), even cybersecurity-aware targets are prone to hacking. This was a nice challenge though.
Now, if you’ll excuse me, I have to anxiously wait for my SAT score while scrolling through r/sat.