Skip to main content

Day 5: Warehouse Maintenance

challenge description
Elves are out of control! They have compromised the database of Santa's warehouse. 
We have revealed the endpoint and we need to find a way to execute commands in the database.
Unfortunately, every command needs to be signed by an Elf named Frost. Can you find a way in?

Evaluating the source

Only a single Python source is provided.

challenge.py
import signal
import subprocess
import socketserver
from random import randint
import json
import hashlib
import os
from util import executeScript


salt = os.urandom(randint(8,100))

def create_sample_signature():
dt = open('sample','rb').read()
h = hashlib.sha512( salt + dt ).hexdigest()

return dt.hex(), h

def check_signature(dt, h):
dt = bytes.fromhex(dt)

if hashlib.sha512( salt + dt ).hexdigest() == h:
return True

def challenge():
print("Welcome to Santa's database maintenance service.\nPlease make sure to get a signature from mister Frost.\n")
while True:
try:
print('1. Get a sample script\n2. Update maintenance script.\n> ')
option = input().strip()

if option=='1':
data, sign = create_sample_signature()
payload = json.dumps({'script': data, 'signature': sign})
print(payload + '\n')
elif option=='2':
print('Please send your script and its signature.\n> ')
resp = input().strip()
resp = json.loads(resp)
if check_signature(resp['script'], resp['signature']):
script = bytes.fromhex(resp['script'])
res = executeScript(script)

print(res+'\n')
else:
print('Are you sure mister Frost signed this?\n')

else:
print('There is no such an option.\n')
exit(1)
except Exception as e:
print(e)
print('Invalid payload. Bye!')
exit(1)


def main():
try:
challenge()
except:
pass

if __name__ == "__main__":
main()

The main vulnerability lies in the first part of the signing. This is a classic known plaintext and ciphertext attack. With the sample plaintext provided, we can simply append more data to the payload and pass in the final hash back into SHA512 as an intermediate state. SHA512 will then generate a new correct hash based on the new block with our supplied data.

Attacking

The hash extension attack is utilised. I have used the HashPump tool, together with its Python counterpart to generate the respective payloads and bruteforcing the key length, since we know it is from 8 to 100.

Luckily, the key stays constant for the life of the Docker container instead of changing for every connection attempt!

Looking at what the new payload does:

util.py
import mysql.connector

def executeScript(script):
mydb = mysql.connector.connect(
host="localhost",
user="root",
password=""
)

mycursor = mydb.cursor()
lines = script.split(b'\n')

resp = ''
for line in lines:
print(line)
line = str(line)[2:-1]
mycursor.execute(line)
for x in mycursor:

resp +=str(x)
mydb.close()
return resp

We can thus easily append more data by adding null bytes to pad the remaining length of the last block and appending new data. This works because the elves made a mistake by splitting by newlines and adding a comment in the sample script. Cheese holes aligned!

solve.py
from pwn import *
import json
import hashlib
import os
import hashpumpy

# Original sample script provided
SCRIPT = 'USE xmas_warehouse;\n#Make sure to delete Santa from users. Now Elves are in charge.'
p = remote('138.68.144.222', 30582)

p.recvuntil(b'>')
p.sendline(b'1')

data = p.recvlineS().strip()
stuff = json.loads(data)

script = stuff['script']
sig = stuff['signature']
print(script)
for i in range(8, 101):
new_h, s = hashpumpy.hashpump(sig, bytes.fromhex(script).decode(), "\nSELECT * FROM materials;", i)
p.sendlineafter(b'>', b'2')
newJson = {"script": s.hex(), "signature": new_h}
p.sendlineafter(b'signature.\n', json.dumps(newJson))
received = p.recvlineS()
print(received, i)
if not 'mister Frost' in received:
break

We obtain the flag, and in my case, the key length is 81!

Flag

HTB{h45hpump_15_50_c001_h0h0h0}