hack.lu CTF 2016 - Cornelius1 - Crypto Challenge

Reading time ~3 minutes

Wasn’t able to login to this CTF until about 3 hours before it was over. So we got what we could solved in that time. This challenge was fun and I’ll go through my solution very fast. Firstly here’s the clue:

Please find Cthulhu’s magic here. Attachment: server.rb

When you visit the link you don’t get much except for a “Hallo fnord” message and a cookie:

 root@kali:~/hacklu/cornelius1# curl -vk https://cthulhu.fluxfingers.net:1505/
*   Trying
* Connected to cthulhu.fluxfingers.net ( port 1505 (#0)
* found 173 certificates in /etc/ssl/certs/ca-certificates.crt
* found 696 certificates in /etc/ssl/certs
* ALPN, offering h2
* ALPN, offering http/1.1
* SSL connection using TLS1.2 / ECDHE_RSA_AES_256_GCM_SHA384
*    server certificate verification SKIPPED
*    server certificate status verification SKIPPED
*    common name: cthulhu.fluxfingers.net (matched)
*    server certificate expiration date OK
*    server certificate activation date OK
*    certificate public key: RSA
*    certificate version: #3
*    subject: CN=cthulhu.fluxfingers.net
*    start date: Fri, 14 Oct 2016 18:27:00 GMT
*    expire date: Thu, 12 Jan 2017 18:27:00 GMT
*    issuer: C=US,O=Let's Encrypt,CN=Let's Encrypt Authority X3
*    compression: NULL
* ALPN, server did not agree to a protocol
> GET / HTTP/1.1
> Host: cthulhu.fluxfingers.net:1505
> User-Agent: curl/7.50.1
> Accept: */*
< HTTP/1.1 200 OK 
< Server: nginx/1.4.6 (Ubuntu)
< Date: Mon, 24 Oct 2016 07:46:11 GMT
< Content-Length: 11
< Connection: keep-alive
< Set-Cookie: auth=N26jjGI5D17rlq0Y8wEjr0BhACaYTwVWKpidTf+JGweC5PA=
* Connection #0 to host cthulhu.fluxfingers.net left intact
Hallo fnord

Looking at the server.rb code, it’s a simple Ruby web server. The one thing it seems to do is take the HTTP GET parameter user, json encode the username with the flag and then send it back to the web client as an AES encrypted cookie.

def get_auth(user)
  data = [user, "flag:"+File.read("flag.key").strip]
  json = JSON.dump(data)
  zip = Zlib.deflate(json)
  return Base64.strict_encode64(encrypt(zip))
class Srv < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req,resp)
    user = req.query["user"] || "fnord"
    resp.body = "Hallo #{user}"
    resp.status = 200
    puts get_auth(user).inspect
    cookie = WEBrick::Cookie.new("auth", get_auth(user))
    resp.cookies << cookie
    return resp

Oh hang on, we missed a step in our earlier explaination. Right before the server encrypts the newly minted cookie, it compresses it with zlib. Why is that a problem? Well the issue is that we can control some of the contents of the cookie. When we can control some of the pre-compression payload, we can attempt to leak information about the plaintext by trying different combinations of inputs and measuring the length of the output. Any input that results in a shorter output than some baseline, could be deduced to be a result of the compression algorithm doing it’s thing and compressing away duplicate data. Thus we leak the plaintext. Let’s test our theory with a couple of curl commands. Since we know the flag begins with flag: we can

root@kali:~/hacklu/cornelius1# curl -v https://cthulhu.fluxfingers.net:1505/?user=ABCDEFGHIJKL 2>&1 | grep auth=
< Set-Cookie: auth=e2RvtidZH9OO7VzGftXRF0/QAMOcpySe6KMMAprdiL+OfaW7UwvxTFgF

This gives us a 56 byte auth cookie.

root@kali:~/hacklu/cornelius1# curl -v https://cthulhu.fluxfingers.net:1505/?user=flag:ABCDEFG 2>&1 | grep auth=
< Set-Cookie: auth=4RC9KuUVjxpsaZewvUhA7Anw0Z1NYv/LKFZnze1jk+lGHcAvyg==

This gives us a 52 byte auth cookie. Interesting! Can we simply deduce further data this way? I code a quick Python requests loop to see:

import requests,string
s = requests.Session()
for c in string.ascii_letters:
    r = s.get("https://cthulhu.fluxfingers.net:1505/?user=flag:"+c+"BCDEFGHIJKL")
    print c,len(r.cookies['auth'])
root@kali:~/hacklu/cornelius1# ./a.py 
a 60
b 60
c 60
K 60
L 60
M 56
N 60

Wow ok. So every letter gives us a 60 byte auth cookie except “M”. Using this idea we proceed to code a solution to leak the remainder of the plaintext:

import requests, string
url = "https://cthulhu.fluxfingers.net:1505/"
user = "flag:"
suffix1 = "BCDEFGHIJKL"
s = requests.Session()
baseline = []
while True:
    for i in range(50):
        r = s.get(url, params={'user':user+"#"+suffix1})
        auth = r.cookies['auth']
        before = len(user)
        for c in string.printable:
            userfield = user+c+suffix1
            r = s.get(url, params={'user':userfield})
            auth = r.cookies['auth']
            if len(auth) < baseline[i]:
                user += c
        if len(user) == before:
            print "[*] Flag: flag{"+user.replace('flag:','')+"}"

Which, when we run, got us the flag:

root@kali:~/hacklu/cornelius1# ./solution.py 
[*] Flag: flag{Mu7aichede} 

Interviewing in Tech: Security Engineer & Security Analyst

Landing a job as a security engineer or analyst at a tech company is a significant feat. It requires not only technical acumen but also s...… Continue reading

BSides Sydney 2023 Writeups

Published on November 24, 2023

DUCTF 2023 Writeups

Published on August 31, 2023