github twitter email rss
34c3 junior CTF 2017, crypto (kim)

This is the first crypto challenge I solved which was really interesting to work on.

When entering the given URL from the challenge description, the following link is shown:

Browsing to the /files folder also shows two other files: dont.gif and flag.

Visiting the sample.gif url will show a sample.gif file. Changing the hash in this URL results in a redirection to a dont.gif file which might indicate that this might be a MAC preventing an attacker to fiddle with the f parameter and accessing other files.

This can be confirmed when looking at the provided source. I only included the relevant parts here:

def mac(msg):
    return hashlib.sha1(SECRET + msg).hexdigest()

def download(umac):
    delim = msg = ''
    for k,v in request.query.allitems():
        msg += delim + k + '=' + v
        delim = '&'
    if mac(msg) == umac:
        return static_file(request.query.f, root='./files')
        return redirect('/files/' + mac('f=dont.gif') + '/?f=dont.gif')

Because all GET parameters are concatenated and a MAC is created from a secret and this concatenated string, this code is vulnerable to a hash length extension attack. By using the sample.gif filename and the given hash as a starting point, we can generate a new hash for a different url/filename, allowing us to request and download other files. A very good summary of this attack can be found here.

Because the length of the secret is not known, I tried different lengths and was finally able to retrieve the flag. For doing the actual hash length extension attack, I used a small script called hlextend which can be found here.

The final exploit code:

import hlextend
import urllib
import urllib2

append = '&f=flag'

for i in range(1, 20):
    print("current: %d" % i)

    sha ='sha1')
    s = sha.extend(append, 'sample.gif', i, '952bb2a215b032abe27d24296be099dc3334755c', True)
    urldata = {'f': s[:-len(append)]}
    url = '' % (

    print('Trying %s' % url)
    resp = urllib2.urlopen(url).read()
    if '34C3' in resp:
        print('Flag found: %s' % resp)

Running this gives us the flag: