3/ Flag 0x01
4/ Flag 0x02
5/ Flag 0x03
5.1/ MySQL SSRF
6/ Flag 0x04
6.1/ Flask SSRF
7/ The End
It’s the first time that I discover CactusCon, an online conference (couldn’t be done otherwise by the time being…:]). The latter is followed by a CTF of 7 hours. Challenges remain accessible afterwards during the conference.
CactusCon9 talks can be watched on Youtube and I recommend that you take a look at it.
I wanted to describe a series of Web challenges in the CTF that I appreciated. I’m also pleased to be the only player to successfully complete below scenario, that’s why I feel I had to do this write-up :)
Take Back C0ntrol is a series of 4 Web challenges, the expected way to solve the first step is by exploiting a blind XSS. However, it seems that the submitted payloads by the players broke the admin bot and so it couldn’t be solved that way anymore :(
I’ve abused a broken access control to get my way to the admin panel. The next steps relies on the exploitation of an SSRF through a remote CSV file loading. It is asked to trigger an easy LFI, then to get you way into an unprotected MySQL database and an authenticated Flask API.
One can register to the application. The associated form contains a name, email and message fields. The latters are vulnerable to blind XSS when rendered to the admin bot. The application is exposed at
I started this challenge at the beginning of the event. At that time, it was possible to exploit the XSS injection. However I stopped after getting the first hit from the admin bot. I identified the location of the latter through the Referer header (
The next day, I noticed it was no longer possible to get a hit from the admin bot. I supposed that the latter was broken due to the submitted payloads from the players.
I tried to reach the admin endpoint from which the bot was hitting me, I just supposed the IP might differ just because of the network interfaces!
It appears that the script does exist on the public IP address and that no access control is done! It was then possible to retrieve the admin password and identify that the XSS payloads were indeed harmful enough to block the bot from working. No one could have solved the challenge through an XSS at this state, a restart of the challenge was mandatory (which hasn’t been done since the CTF was over).
By logging in on the application as the administrator, I figured it was possible to interact with an import feature and to retrieve the application’s configuration. The latter couldn’t be modified, but suggested that some internal services might be reachable. Moreover, some message within the inbox of the admin account are stating about those services.
The import feature seems to load a remote CSV file from which the expected columns are specified within the error message:
The associated request is a POST one to the submitted URL and the feature choosed (import).
While not having access to other functionalities such as the export one, I still tried to modify the import feature by the export within the request. I got the first flag through this small tricks even if it didn’t allow me to perform an export (in fact I could have asked for any other dummy feature, I would have retrieved the flag):
The flag’s content is also suggested that it was expected to exploit the blind XSS.
The rest of the challenge will focus on the exploitation of the import feature.
After creating the CSV file with the expected columns over my VPS, I figured out that there was an SSRF within the
catalogURL column. The second challenge description specified the need to retrieve the content of the file
/flag (thanks to state about it, it could have been a boring guessing time…).
The content retrieved from the SSRF is encoded with Base64.
It is now time to hunt for bigger! There are Memcached, MySQL, Elastricsearch and Flask services configured on the application environment. The SSRF exploitation over the Memcached and Elasticsearch services didn’t allow to go further in the solving of the challenge, so I won’t state about it any further.
According to the inbox messages, the MySQL database was left unprotected. It was then possible to perform an SSRF in order to retrieve the database content. The tricks ? It was not the
root account that didn’t have a password but
brian’s one! After trying to exploit directly the SSRF over the MySQL service or by finding another SSRF affecting the Memcached/Elasticsearch services (in order to exploit the MySQL service and appears to come from the IP 10.10.10.53 - localhost rather than 10.10.10.52), I asked the challenge author if it was doing it wrong or if the challenge was broken at this step. He told me to look again at the inbox message, that’s how I figured out about the brian’s account.
Indeed, it was possible to interact with the database when specifying the brian account. I relied on Gopherus to exploit the SSRF on MySQL.
Below is an example of the payload generation through Gopherus over MySQL service. As suggested, the tool relies on the gopher protocol:
That payload has be defined within the remote CSV file:
After sucessfully exploited the SSRF, we finally retrieve the informations we were looking at:
We discovered “settings” table within the “webnote” database. When retrieving the content of the latter we got the flag and what seems to be an API key:
The last part involves the authenticated Flask API as stated in one of the inbox message.A Web service should be opened in the range 6600 to 6700 of 10.10.10.54 ports.
After I generated as many CSV file as tested port on the IP address of the Flask service IP address (10.10.10.54), I performed a bruteforce using patator. The port 6666 is quickly identified:
The Web service is systematically answering the following:
The inbox message stated about
x-apikey header, which makes sense.
We retrieved an API key within the database
MYSECRETAPIKEY. It now requires to exploit the SSRF through the gopher protocol in order to perform an HTTP request with the needed header.
Indeed, the simple use of the HTTP protocol doesn’t allow to include the required header while exploiting the SSRF, it is mandatory to rely on gopher.
The final payload within the remote CSV file is the following:
Once the answer decoded, we got the last flag:
I’ve been in trouble while looking for the API key because I thought the latter was the content of the third flag… I was trying numerous combination of the needed header that could have allow an API authentication, without sucess. Ryan (challenge’s author) told me I was mislead, thanks for your help and the challenge you made Ryan. I personally appreciated the various exploitation of the SSRF.
Thanks to CactusCon9 organizers!