iCTF 2013: uranus 2

Due to the code listings and some blah, this write-up is quite lengthy. Prepare some coffee first. 😎


The uranus service from iCTF 2013 (code) is a node.js service written by @kapravel which has to be deobfuscated. Using jsbeautifier.org we get a better look on the code:

For more PITA, the authors used ternaries for almost all variable assignments, e.g. s = r3A8.J8("c14") ? "match" : "toString", and the following function to determine the correct value for each variable:

For each ternary the condition is determined by calling r3A8.J8() with a hex string as argument, so we start digging there.

We see that in line 13 of the above code the comparison with r3A8.U8() always yielded true for the duration of the iCTF 2013: r3A8.U8() returns the current time in milliseconds and the left part of the comparison just yields the fix value 1386457200000, a timestamp in milliseconds equal to 2013-12-08 00:00:00 UTC. Also, R8 = 0.

In the for loop the LSB of each char (interpreted as hex digit) of the supplied string is XORed with the others. The resulting bit is returned as Boolean.

A more readable version of r3A8.J8() is as follows:

So basically this function determines the return value by just counting all odd hex digits to see if the total count is also odd.


  • 9abc – false (2 odd digits: 9, b)
  • 9abcd – true (3 odd digits: 9, b, d)

We write a short script which determines the correct assignment for each variable and clean up the whole mess by removing any parts which are not necessary anymore. The result is the following quite readable version of the code which can be used in place of the original one:


The service only accepts POST requests which contain specific JSON data, other requests are dropped with an info message. The JSON data of each POST request is intercepted for flag_id, token and code properties. At least the properties flag_id and token are required.

If only the code property is missing, the service tries to read a file with the name flag_id + “-” + token from the submissions directory. If such a file exists the service returns the first password that matches /password = "([0-9a-zA-Z]{16}"/ within that file. Since (surprise!) password = flag, this is how flags can be retrieved from the service.

If all three properties exist, the supplied code is checked for unwanted statements and then, if found to be valid, executed in global context. This means that we can modify global variables (like password) with our code. After that, the length of the global password is checked and, if not zero, the supplied code is stored into a file with a file name derived from flag_id and token, as mentioned in the previous paragraph. Since the supplied code must modify password in order to pass the subsequent length check and get stored, this is how new flags are pushed to the service.


To get any flags from this service we need to know the exact name of the file which contains a flag – unfortunately, only the first part of such a file name is provided: the flag_id available in the exploit template. However, we can use the code property to run some node.js code on the server which deals with this problem:

First we create a POST request with an arbitrary flag_id and token and include code that uses the node.js fs module to search the submission directory for the file with the name that contains the actual flag_id provided by the exploit template. Then we read the found file (there should only be one), retrieve the line where the password assignment happens and write the whole thing into a new file with a unique name, e.g. all-your-wizardry-are-belong-to-us. Since we do not explicitly set the global password our code itself doesn’t get stored at all.


Using a second POST request we retrieve the password which we just stored: We omit the code property and provide the unique file name from our first request via flag_id and token. Therefore, the service gladly reads the file and provides us with the password. Flag captured.

Example exploit using the template:


To detect attacks we simply look for any occurrences of readdir in the uranus traffic.


A patch can be applied by preventing any calls to fs.readdir() or fs.readdirSync(). This can be done by simply adding readdir to the array with unwanted code in checkCode():

However, as mentioned in this comment, the above approach is still vulnerable to obfuscation techniques.

Leave a comment

Your email address will not be published. Required fields are marked *


2 thoughts on “iCTF 2013: uranus

  • Sebastian Neef


    first of all: cool write-up!

    I think that your patch would still be vulnerable to attacks. You simple could use JSFuck (jsfuck,com) to obfuscate the payload and bypass your filter. Blacklistfilters are bad 🙁

    We patched the service using the following patch:
    1. Create a new sandbox: e.g.
    sanbox = {
    password: ”,
    ff: ”,
    //some more global vars
    2. Use vm.runInNewContext(code,sandbox)
    3. return the new password variable

    The javascript only has access to the variables listed in the sandbox-variable.

    That fix worked good for us 🙂 (or better: It seemed to work)

    Best regards,
    Sebastian N. (@internetwache)