Capture the flag events are particular fun events done to challenge people and get people to really think about the puzzle they’re presented with. They can be in many different types and variations from cryptography style puzzles to a vulnerable web application, but they all have one thing in common and that is finding a flag which can be a string text denoting that it’s the flag. The flag is of course thoroughly hidden and will require some work to find. The first thing we always want to do with capture the flags is pay attention to challenge details since they always contain pertinent information that will more likely come in handy later on in the challenge.
Here is the challenge prompt:
An engineer of acme.org launched a new server for a new admin panel at http://104.236.20.43/
He is completely confident that the server can’t be hacked. He added a tripwire that notifies him
when the flag file is read. He also noticed that the default Apache page is still there, but
according to him that’s intentional and doesn’t hurt anyone. Your goal? Read the flag!
When we first take a look at the server by visiting the IP, we are presented with an apache default page being shown for 104.236.20.43. From this we know that it’s using Apache and we also might as well check for a flag file on any of the directories. Doing so, we then find 104.236.20.43/flag to see a fake flag, but this may be useful for later on as challenge creators doing similar things intentionally as a hint towards the participants.
We then go over to view acme.org and see if resolves to anything, which it doesn’t. Now if you don’t know anything about virtual hosts, they work by checking the value submitted in the Host header of a HTTP request, basically a way for a server to map a host name to the server. We have a host name already, remember acme.org?
Since we’re looking for an admin panel, we should first try sending a host header with admin.acme.org which will resolve to a blank page. An alternative method to make this easier, is modifying your /etc/hosts file and then adding 104.236.20.43 admin.acme.org. This way when we visit admin.acme.org it’ll resolve with the correct IP.
After we correctly set up the virtual host and we visit the domain name we now are presented with a blank page and we observe that we’re given a cookie named admin=no.
The next step is obviously tampering with the cookie and setting it to true although this results in a HTTP 405, method not allowed error meaning we need to find the right HTTP verb to send to the server.
A HTTP verb or HTTP method is used to dictate an action to perform to the website, GET is the most commonly used action. The first thing we tried when visiting the website was a GET request. We can use burp suite repeater or curl to replay the request to change and variate the methods to use. First let’s test an obvious method and that is a POST method.
This will result in a new error, HTTP 406, not an acceptable response. This means our content-type isn’t accepted by the server and we need to change our content-type to an accepted one. Back in burp we can specify our content-type by adding content-type: example/example to our request and then modifying example/example with different content-types to see what will be accepted.
After a bit of googling for different content-types (application/html, text/xml, etc) one of them did finally work: application/json. When we submitted this, we receive the following response: {“error”:{“body”:”unable to decode”}} with the HTTP error 418.
Now it looks like we can finally submit data to the server. To figure out exactly what to submit,we submit a basic JSON request like this: {“test”:”test”}, which the server responds with a different message {“error”:{“domain”:”required”}}.
We’re now getting somewhere, it looks like it’s asking for a website to be submitted with the parameter Domain. Here I thought maybe it’s asking for the acme.org host name again, so let’s submit {“domain”:”admin.acme.org” } and we’ll receive the following, {“error”:{“domain”:”incorrect value, .com domain expected”}}.
At this point I decided to correct it and changed the TLD to .com instead. We proceed to receive another error, {“error”:{“domain”:”incorrect value, sub domain should contain 212″}}.
We then change admin to 212.acme.com to finally receive this response {“next”:”/read.php?id=307″}.
Let’s go visit the php file now by going to, admin.acme.org/read.php?id=307 we see {“data” “ “}
This points to the possibility if we feed a valid 212 sub-domain into the website then we may receive a different response in the page. Hopping over to google.com, we will google dork for a 212.*.com website, by using site:212.*.com in google and we will find several different sites using and I ended up using 212.njzhuoding.com. When I posted it to the admin.acme.org we end up with {“next”:”/read.php?id=308″}
Visiting the php page this time will reveal a base 64 encoded response which when decoded reveals the html source code. This means several things actually: first, this end point queries a website and then base 64 encodes the web page before storing it on admin.acme.org. Second, we have a possibility of server side request forgery (SSRF) vulnerability. Third, we will need to bypass the sub-domain requirement and the .com requirement if we want to use this end point to find the flag.
SSRF is an exploit in which we leverage a vulnerable application that makes requests to an attacker provided URL which could potentially lead to accessing internal services or areas where a user wouldn’t be normally able to access by browsing the website.
First step we need to do is find a way to bypass the sub-domain requirement and the TLD requirement. One way we could do this is using carriage return line feed (CRLF) characters to make new lines in the request itself, meaning we can make it fulfill the requirements but have the application query each segment of the request independently.
CRLF characters are used to denote the end of a line. Since this is server is using a linux distribution, we can just use LF or n to terminate the line. Here’s an example we would send a request like 212.nacme.orgn.com then the server would see this as, make a query to 212. then make a query to google.com then make a query to .com.
An important thing to note is that, as mentioned earlier, each request makes the ID number go up by 1. A request like the previously mentioned would create three new id, one for each request. If we sent request like this, {“domain”:”212.nacme.orgn.com”} then our response would be {“next”:”/read.php?id=311″}
If we want to see acme.org response, we will then subtract one from the id number and get id=310. When we visit the page, we see the base64 encoded page as expected which. Now we can continue to the easier part of the challenge, exploiting the SSRF.
Now that we don’t need to worry about TLD or sub-domain requirement any more, let’s access the localhost with port 80 like this {“domain”:”212.nlocalhost:80n.com”}
When we base64 decode this response, we receive the default Apache installation page as expected thus confirming that we can access the local environment. It maybe possible that the flag is located in the same place as the fake flag but on an internal port. First, we should try some common ports like 8443, 8080, 443, and 22.
We will check ports using the same format as trying port 80, when we submit port 22 then decode the response on read.php it will give us the SSH version and OS with an additional message about mismatching protocols. All but port 22 and 80 resulted in a response, so there has to be an uncommon port being used. Since this is a CTF it would be a good idea to check 1337 since this is quite well known hacker lexicon. When we try port 1337 we get the following base 64 encoded message,
{“data”:”SG1tLCB3aGVyZSB3b3VsZCBpdCBiZT8K”}
This decodes to “Hmm, where would it be?” We’re close now, let’s try localhost:1337/flag, as it’s obvious now that was a hint to when we got to this point.
This will gives us
{“data”:”RkxBRzogQ0YsMmRzVlwvXWZSQVlRLlRERXBgdyJNKCVtVTtwOSs5RkR7WjQ
4WCpKdHR7JXZTKCRnN1xTKTpmJT1QW1lAbmthPTx0cWhuRjxhcT1LNTpCQ0BTYip7W
yV6IitAeVBiL25mRm5hPGUkaHZ7cDhyMlt2TU1GNTJ5OnovRGg7ezYK”}
This when decoded will reveal our flag,
FLAG: CF,2dsV/]fRAYQ.TDEp`w”M(%mU;p9+9FD{Z48X*Jtt{%vS($g7S):f%=P[Y@nka=
This will give us the flag and we have completed the challenge successfully. An alternative method to finding the port would be running through all ports then checking responses, this would be intensive and quite tedious but is feasible with burp intruder.
Capture the flags do sometimes include intentional nods to the hacking community as a whole which made finding the flag quite easy. Overall this challenge was quite straight forward and required very little in depth knowledge about exploiting web applications. Although I didn’t win the challenge, people who submitted valid writes would receive a challenge coin.