CTF Team at the University of British Columbia

[Dragon CTF 2021] CTF Gateway Interface

30 Nov 2021 by - Angus

Problem description

This is the problem entitled “CTF Gateway Interface” from Dragon CTF 2021.

You are given a web interface to a server that supports CGI and a generic password prompt. The details of CGI aren’t too important; it just provides a framework for running server-side scripts to generate web content.

Entering a password triggers the following sequence:

  1. The startAuth.cgi script is run with the given password, returning a session ID.
  2. One second later, the authResult.cgi script is run with the session ID from the previous step.
  3. An authentication success or failure message is shown. (This isn’t important to the challenge.)

The goal is to run the script named x in the same folder as the other scripts. The server explicitly prevents you from running it directly.

Figuring out a plan

The first script takes the password, salts it with a provided string, SHA-256-hashes it, and writes the hash as raw bytes, rather than a hex string, to the script folder under the name session_[session ID]. The second script reads the hash file and deletes it, after a delay of one second.

This means that there’s a window of one second where we can execute the hash file as a script. If we can make it execute x, then we’ll get the flag.

So the problem becomes: how do we generate a raw binary hash that can do this?

What doesn’t work

We could try making our hash file a symlink, but there’s no way to do that by only writing to the file.

We could try making a minimal executable that makes a system call, but this article claims that the smallest possible ELF executable is 45 bytes, which is larger than our 32-byte hash. In any case, it’d be nearly impossible to craft a hash with the required bytes.

What does work

There’s a clue in the provided sample x script:

print("On the server this would be an executable that outputs the flag.")

Namely, the shebang operator is used to run /usr/bin/python3. It turns out that relative paths are supported, meaning if we can make our hash file begin with #!x, we can make it execute x. However, we also need to make sure the fourth byte is either a space or a null byte, which prevents the other bytes from interfering. Otherwise, the server will try running a script named x[bunch of random bytes], which won’t work.

Thus, we can test strings using something like the following:

import hashlib

def test(s):
    string = f"SaltyMcSaltFace{s}".encode()
    sha = hashlib.sha256(string).hexdigest()
    if sha.startswith("232178") and (sha[6] == "0" or sha[6] == "2") and \
        sha[7] == "0":
        print("good", s, sha)

I tried this with integers and eventually found 1017424411, which gives this hash file:

00000000  23 21 78 00 75 c1 52 b4  cb 17 4d b0 eb a1 50 74  |#!x.u.R...M...Pt|
00000010  62 54 c5 25 cb 73 09 cd  92 92 5f b4 37 19 3f 0a  |bT.%.s...._.7.?.|

Running this locally executed the x script in the same folder, so this is what we want.

Putting it all together

We just need to:

  1. Run startAuth.cgi with the password from above, saving the returned session ID
  2. Run session_[session ID] with the given session ID
  3. Read the output

Using a terminal (because I’m lazy and didn’t want to write a Python script):

$ curl http://ctfgatewayinterface.hackable.software:8888/cgi-bin/startAuth.cgi?password=1017424411 2>/dev/null |
> jq '.sid' |
> xargs -I{} curl --http0.9 "http://ctfgatewayinterface.hackable.software:8888/cgi-bin/session_{}"