Table of Contents
Skylark Capsule CTF Challenge
Introduction
“Skylark Capsule” was a web challenge worth 200 points in the Hacky Holidays 2021 “Space Race” CTF challenges. This challenge was split into two parts both worth 100 points each and was described as medium difficulty. A total of 77 people (including me) completed this challenge.
The challenge homepage looked like this:
The login page:
The register page:
Part 1: "Tokenz"
Challenge description: “Get your capsule now. Can you gain access to the capsule of 'admin'?”
After quickly checking for interesting HTTP headers or comments in the page source, I decided to create a test account using the register page. I had the Firefox developer tools open from this point onwards. I signed up using “test” for both my username and password. I then signed out and back in to ensure my account was fully saved and working.
I visited the capsule page as my “test” user and noticed the following string was logged to the developer console:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImlkIjo1LCJ1c2VybmFtZSI6InRlc3QiLCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJwYXNzd29yZCI6Ii02NjI3MzMzMDAifSwiaWF0IjoxNjI3NDAyOTQyfQ.1RjOb4DokyxL_MrdDNXkrYw2ygEjb-_-vuVdMNuJWg8
I recognised this as Base64 and decoded it into the following human-readable text:
{ "alg": "HS256", "typ": "JWT" }{ "data": { "id": 5, "username": "test", "email": "[email protected]", "password": "-662733300" }, "iat": 1627402942 }
(I have also formatted it so it is easier to read)
I recognised the decoded text as a JWT (JSON web token) object but the “JWT” value in “typ” also confirms it. Since the challenge is called “Tokenz”, I assumed I needed to attack this token to gain access. I used JWT.io instead of decoding the Base64 directly from this point on.
JWTs are signed to prevent end-users from altering them. Since this website stores the currently authenticated user in a token, an attacker could take over any account if the token could be modified by the attacker; this is why signing the token correctly is important.
There are multiple ways to sign a JWT and the method which the server is using is described in the “alg” variable – this website is using “HS256”. RFC7518 gives a list of valid methods and what they correspond to. For example, HS256 is “HMAC using SHA-256” which relies on a shared secret (aka a password) instead of using asymmetric encryption which would use public/private keypairs.
Since the only thing protecting the JWT is a password, we can attempt a brute force attack against it. The following Hashcat command is able to brute force JWTs using a wordlist:
hashcat -a 0 -m 16500 jwt.txt rockyou.txt
“-a 0” denotes a dictionary attack and “-m 16500” instructs Hashcat to attack a JWT. The full Base64 encoded string is stored in “jwt.txt” and “rockyou.txt” is a list of passwords to try.
Hashcat was able to recover the following password: “skylark140584”.
Now we have the secret which the server uses for JWT signing, we can edit the token to anything we want and the server should accept it. I used jwt.io again to edit the token and to sign the new token for me using the discovered password. See the screenshot of how I used the tool below:
I simply pasted the Base64 payload from the server on the left, edited the JSON object on the right, and pasted “skylark140584” at the bottom. This updated the Base64 on the left in real time so I could copy it back out again.
I used “admin” for the username and changed the id to 1. I didn't change the password value.
Now we have a valid token with admin credentials we should theoretically be able to solve the first challenge. I went back to the challenge webpage and replaced the old Base64 payload with my newly generated payload into the browser local storage. I then pressed the “get specs” button on the capsule page and was immediately given the flag.
This challenge demonstrates the importance of using a strong secret on the server for JWTs. The entire user authentication scheme of this website can be completely compromised by the single string “skylark140584” which takes just seconds to discover using Hashcat.
Part 2: "Hashing"
Challenge description: “Skylark is making use of super safe non-cryptographic hashing algorithms. Can you log in as the admin?”
I had already noticed the “password” value in my JWT looked very short. For comparison, here is “test” (my fake password) hashed using SHA256:
9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
And here is the hash which the website generated for “test”:
-662733300
Immediately we can see this hash is much shorter than it should be. If you ever find a password hash this short, you know something is wrong.
This website has another vulnerability. Upon submitting my new JWT in the previous challenge, the server responded with this payload (only visible using the “network” tab in the developer tools):
{ "status": 200, "data": [ { "id": 4, "username": "admin", "email": "[email protected]", "password": "-432570933" } ], "flag": "CTF{redacted}" }
The hashed password value (-432570933) for the admin user was given to me by the server. Since we now have the hashed password, we can perform offline attacks which are significantly faster than brute forcing the password over HTTP(S). For that reason, sending passwords back to your visitors is a bad idea even if you think they are authenticated.
I used the “hash-identifier” software which came bundled with my Kali install to help me determine which hash the challenge was using. It returned no results for “-662733300” so I converted it to hex which yielded 2 possibilities:
-------------------------------------------------- HASH: -662733300 Not Found. -------------------------------------------------- HASH: D87F7E0C Possible Hashs: [+] ADLER-32 [+] CRC-32 Least Possible Hashs: [+] CRC-32B [+] XOR-32 --------------------------------------------------
I confirmed that CRC32 was the correct algorithm by using CRC32.online to hash the value of “test” which matched my password hash (-662733300 / D87F7E0C)
Since CRC32 is designed for detecting transmission errors rather than for hashing passwords, it should be quite easy to cause a collision where multiple values have the same hash.
I used this tool to generate a collision: https://github.com/theonlypwner/crc32 with this command:
python crc32.py reverse -432570933
This tool gave me multiple strings which would match -432570933 when CRC32'd and it only ran for about a second. I was given the second flag as soon as I entered one of the generated “passwords” for the admin account.
Summary
I enjoyed this challenge because it showcases some very common vulnerabilities which are seen in “real” websites and it also shows the huge impact that a weak/reused password can have.
If you want to know how I solved other Hacky Holidays challenges, you can read a brief overview I wrote about most challenges I solved.