H1 212 CTF Write-up

Abiral Shrestha
10 min readNov 19, 2017

As usual, I woke up and started scrolling the Twitter. The first thing I noticed after opening the twitter was the tweet of Jobert Abma about the Capture The Flag competition. Hackerone was sponsoring trip to NYC for the winner of CTF. I was excited but since this was the first time I was participating in any CTF, I thought I would not crack it. However, I thought of giving a try and jumped into description.

The IP address of the target was given which contained Apache2 Ubuntu Default Page.

Apache2 Ubuntu Default Page

Port Scanning: IP address of the target was given, so the first task performed was port scanning of the target. A quick port scan for common ports showed the following result.

Port scan for common port against target

Port 80 as mentioned in description of CTF served “Apache2 Ubuntu Default Page”. To find out, if the default page was modified(consisted some hint), I ran a quick diff between the index.html page of the target and some random site searched from Shodan, serving similar Default page. However, the result of the diff command showed, that the default page did not consisted any new information.

As obvious port 22, was open for OpenSSH service. As nothing interesting was found here, I moved on to next step and performed full port scan on the server, which also did not yield anything interesting.

Some Failed attempts:

At first, I manually browsed through the endpoints that could have contained some hint like /robots.txt, /security.txt, /humans.txt, /jobs.txt, /.git, /.svn, /sitemap.xml, /crossdomain.xml, /flag.txt, /flag etc. I got noting except the following output on /flag endpoint, which asked me to keep digging.

I kept digging. I fired gobuster and bruted endpoints with different dictionaries but could not find any interesting endpoints.

Virtual Host Discovery:

As IP address of the target was given not the domain-name, I thought giving a try to discover the virtual host of the given IP would be worth it. Virtual host discovery by Jobert Abma required passing host-name as one of the options but I had no idea regarding what hostname to use.

Usage of Virtual Host Discover

Ipv4 lookup for the target IP address showed hostname for the site to be http://tequilawolf.codedecode.com.br.

ipv4 info of the target

Hence, I fired virtual host discovery with hostname “codedecode.com.br”. The response for every host header was same so I thought that application was ignoring the host header and routing requests with any host header to same application. So, I moved on.

Subdomain gathering: Sublister was fired to see if there were any other sub-domains containing juicy information.

Gathering Subdomain against codedcode.com.br

Web-screenshot was used to take the screenshot of the discovered sub-domains. But before that, the sub domains that did not resolved to IP address was filtered out.

Sorting domain and running web-screenshot

The screenshot were reviewed but did not contain anything interesting.

Next day, when I woke up, I saw this tweet from Jobert Abma.

This tweet made me review the things that I have done. I thought of starting from the beginning to make sure that I have not missed anything.

I started by reading the description of CTF again and suddenly realized that I had missed an important detail.

I had only tried to discover virtual host with sub domains of codedecode.com.br but did not bothered trying it with subdomains of acme.org. I opened BurpSuite and send request to 104.236.20.43 with value of Host header as acme.org. I was greeted with the same Apache2 Ubuntu Default Page.

After that I sent request with admin.acme.org as the value of Host header to 104.236.20.43 and I got an exciting response.

Finally, I got something that was not Default Page. The site was returning an empty page that was setting cookie with name “admin” with value “no”.

Without any delay, I sent another request by setting cookie named “admin” with value “yes” and got response with status code 405 stating “Method Not Allowed”.

Again, without any delay I switched method to POST request and got response with status code 406 stating “Not Acceptable”.

406 Not Acceptable is returned when Web server detects that the data it wants to return is not acceptable to the client. However, in our case, Accept header seemed good. I kept playing with Accept, Accept-Charset, Accept-Encoding, Accept-Language and Accept-Ranges headers for a while but that did not stopped me from getting 406 response code.

After a while I thought, may be I am getting the error because I am sending that content type that is not accepted by the server. Hence, I tried changing the “Content-type” to “application/json” and I received following error stating “unable to decode body”

As I was not sending any request body, I tried sending random body in JSON format. This presented me with next error stating that domain was required.

I knew I was going in right path so without any further delay I sent another JSON request with body as {“domain”:”acme.org”}.

The response stated that it was expecting .com domain so I changed domain to “admin.acme.com”.

Again, the response threw some error stating “incorrect value, sub domain should contain 212”. So I changed domain to 212.admin.acme.com.

A new endpoint was received. When “GET” request to given endpoint was sent, JSON response with “data” key and empty value was received.

I tried switching to POST method and sending JSON request with {“data”:”random_data”} but still empty response was received.

I also tried random id like l, 212, 1337, -1. All of them returned error, stating “incorrect row”.

I thought it was hint for SQL injection. I tried for SQLi but could not succeed.

After a while, I decided to move back to the previous endpoint, where I could pass domain. I tried passing endpoint from Burp collaborator, in the hope of getting ping back from the server. However, the URL needed to contain 212 in subdomain, else I would receive error.

Luckily, Burp Collaborator is configured to support wildcard subdomains i.e. requests sent to any subdomain of randomtext.burpcollaborator.net resolves and is received without any complaints. Hence, creating subdomain that contained 212 was easier.

The URL of Burp collaborator ended in “.net” TLD. However, the URL needed to end in “.com”. I tired bypassing this using URL delimiters like “?”, “#”, “%3f”, “%23” but none of those characters were allowed.

However, not all URL delimiters were blacklisted. “/” was still allowed.

Hence, request as shown below was passed.

Request to collaborator was received from IP address 104.236.20.43, which was the IP address of the target server.

The endpoint received consisted of base 64 encoded data which on decoding turned out to be response from burp collaborator. The domain passed is fetched by server and its response is presented to us.

This means we got SSRF on target server. However, the annoying part of this was, subdomain section of URL needed to consist “212" and URL needed to end with “.com”.

I could have created a sub domain starting with 212 and pointing to desired internal address or localhost. But there are numerous public wildcarded domains that points to localhost. I used 212.vcap.me which points to localhost.

To make sure this was working, request to port 22 was sent.

As expected port 22 responded with OpenSSH banner.

To perform port scanning over the localhost, I wrote a small python script that send post request to / endpoint, grabs next endpoint from response and again sends request to the next endpoint.

The script outputs the port number and response from the port in terminal.

My script discovered an open port on 1337 which turned out to be nginx server on Ubuntu. (I should have guessed that instead of writing down script. :P)

I could perform port scan on the server. But I needed to add /.com, in the end of URL. If I needed to read the flag on 127.0.0.1:1337/flag endpoint, I had to send request to /flag/.com endpoint which would give an 404 not found page.

I tried via curl, this would have worked if flag was in some php extention.

I tried some random mutated words with php extentions like flag.php, fl@g.php, admin.php etc. but none of them revealed flag.

Being inspired from Orange’s blackhat presentation, I tried various variation of URLs like admin@212.a5g0odr6ob9jyak7m3yeoox.burpcollaborator.net@.com, in the hope the parser would send request to 212.a5g0odr6ob9jyak7m3yeoox.burpcollaborator.net. But everytime “@” was included in URL, the request would not arrive.

I also tried to turn the SSRF into LFI and RCE but non of them succeed.

As the request from application to burp collaborator did not contain any user-agent header, it was impossible to know what application was using to fetch resource. I thought it might be worth it to give try for blind XSS, if they were rendering the response in application side using PhantomJS. But that did not work either.

I created an subdomain with /.com endpoint that would redirect to local host endpoints like 127.0.0.1/flag but soon I realized the application was not following the redirect.

I needed to figure out a way to make the application ignore /.com at the end. Under normal case this could have been easily achieved by injecting null byte character %00. But it was not working in this case as ‘%’ was not allowed. I also tried various other forms of null byte like ‘\x00' and ‘\u0000’ but none of them worked.

After trying for a while I tried CRLF characters, surprisingly “\n” did the job. The /.com at the end was ignored. When request was sent to endpoint /flag\n.com it would be sent to /flag instead. It seemed as if the URL parser being used treated Line feed(\n) as end of URL and ignored anything beyond it.

I knew I was close. Without any delay I sent request to read flag on 212.vcap.me:1337/flag\n.com.

But there was no response. Normally I would have got 404 page, if the endpoint was not present but this time I got an empty response.

I thought may be I crafted the URL wrong. I went back and sent the request one more time. I saw a strange behavior in the application. Every time i requested a domain with line feed in it, id parameter in the response body would increment by 2 instead of 1.

I hurried up and send request to read.php by decreasing the value of id with 1.

Finally, I got the flag.

FLAG: CF,2dsV\/]fRAYQ.TDEp`w”M(%mU;p9+9FD{Z48X*Jtt{%vS($g7\S):f%=P[Y@nka=<tqhnF<aq=K5:BC@Sb*{[%z”+@yPb/nfFna<e$hv{p8r2[vMMF52y:z/Dh;{6

I am not sure why application was increasing the id with 2 instead of 1, may be the URL parser was not completely ignoring the characters behind Linefeed. I can be wrong, but I think the application is sending 2 requests every time linefeed is included. The first request ignores everything beyond linefeed where as second request sends the request with linefeed in it. May be we were receiving the id of the second request, that was being sent with linefeed in it.

I was so excited and happy that I finally got the flag :D

Thank you Jobert Abma and Hackerone Team for such an amazing CTF. Lessons learned during the CTF have boosted my knowledge on various aspects of web applications like SSRF, URL parsing and more.

--

--