Hack.lu 2014 CTF - Crypto150 Hidden in ρlaιn sιght

Here is a quick write up for the Hidden in ρlaιn sιght challenge of the hack.lu 2014 CTF.

This challenge consisted in a small upload website running in node.js. We were given the code of the application and we were supposed to retrieve the file located in /files/testuser/flag.txt.

We started by taking a little tour of the website.

To upload a file, you first had to register with any credentials you wanted.

Then with the same credentials, you could upload a file.

upload_img

Nice ! But now how to get this file back ?
The uploader had to generate an access link and then share link.

link_img

In this example, I uploaded a file sadcat.jpg with the johnCool account protected by a super secret password. Then I got this token fa2e61d3be1e92fc06c2bc786bc444c4f74e24661f996c596c09f94c89e1deae and the file was reachable with this link .

So to get the flag, we only had to compute the same token than testuser did with the file flag.txt.

We took a look a the sources to understand how this token was created. It appeared to be a sha256 salted hash, and the salt seemed to be random…

Here are the relevent sections.

The random generated salt :

var HMAC_SECRET = ''
for (var i=0; i<20; i++) {
    HMAC_SΕCRET = HMAC_SECRET + (Math.random()+'').substr(2)
}

The signature function :

function hmac_sign(path) {
var hmac = crypto.createHmac('sha256', HMAC_SECRET)
    hmac.update(path)
    return hmac.digest('hex')
}

And the function call in the link generation :

res.redirect('/files/'+user+'/'+file+'/'+hmac_sign(user+'/'+file))

To get the flag we had to know the HMAC_SECRET and we did not want to bruteforce a 160 bits salt sha256 :) .

So we decided to start node and to play a bit with the code to see if something is wrong with it.

> var HMAC_SECRET = ''
undefined
> for (var i=0; i<20; i++) {
...   HMAC_SΕCRET = HMAC_SECRET + (Math.random()+'').substr(2)
... }
'4345081045757979'
> HMAC_SECRET
''

What’s that ??? How HMAC_SECRET could have two values ?

Well hexdump gave us the solution :

hexdump -C hiddeninplainsight.js | grep HMAC
00000100  61 72 20 48 4d 41 43 5f  53 45 43 52 45 54 20 3d  |ar HMAC_SECRET =|
00000130  20 48 4d 41 43 5f 53 ce  95 43 52 45 54 20 3d 20  | HMAC_S..CRET = |   # ?????
00000140  48 4d 41 43 5f 53 45 43  52 45 54 20 2b 20 28 4d  |HMAC_SECRET + (M|
000001b0  2c 20 48 4d 41 43 5f 53  45 43 52 45 54 29 0a 20  |, HMAC_SECRET).

So what happened is that in the loop the variable’s name first ‘E’ is not the 0x45 ASCII character. But it’s unicode U+0395 character wich is the capital epsilon in the Greek alphabet.

So at the end, in the loop we are not updating the right variable and the seed remains a blank string.

Knowing that we could compute the same hash :

> hmac_sign('testuser' + '/' + 'flag.txt')
'4a332c7f27909f85a529393cea72301393f84cf5908aa2538137776f78624db4'

And get the flag : flag{unicode_stego_is_best_stego}

Thanks to Fluxfingers for this funny chall’, I enjoyed it !

Comments !