Pythonの識別子におけるUnicode正規化(NFKC)とpyjail
Unicode正規化とは?
Unicode正規化は「コードポイントや字体が異なる同じ意味の文字は等価とみなすように比較したい!」というときに使うものです。例えば、正規化形式の一種であるNFKCを使うと「ア」と「ア」が等価になります。
詳しくはMDNを読んでください。
Pythonでは
Pythonでは識別子にASCII外の文字を使用することができます。
入力1 = input() print(入力1)
Pythonがソースコードを解析するとき、全ての識別子はNFKCに変換されます。
そのため、このようなコードも動きます。
ア = input() # 全角 print(ア) # 半角
このように字体が異なっていてもNFKC的に等しいなら同じ変数として使用できるんですね。そのため、次のようなコードも動いてしまいます。
# Normal print("Hello world") # 全角 print("Hello world") # bold italic 𝙥𝙧𝙞𝙣𝙩("Hello world") # 上付き文字 下付き文字 ᵖʳⁱⁿᵗ("Hello world") ₚᵣᵢₙₜ("hello world") # いろいろ 𝖕𝖗𝖎𝖓𝖙("Hello world") 𝓹𝓻𝓲𝓷𝓽("Hello world") 𝕡𝕣𝕚𝕟𝕥("Hello world") 𝚙𝚛𝚒𝚗𝚝("Hello world")
Pyjailでは
このような面白い挙動がCTFではどう出題されているのでしょうか?
Filter bypass
code = input() for c in code: if c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890": print("deny") exit() exec(code)
アルファベットと数字が全て禁止されているpyjailです。ASCII内で考えると記号しか使えないように思ってしまいますが、先ほどのテクニックを使うと簡単にbypassできますね。
exec(input())
このテクニックは日本のCTFでは見たことないですが、ところどころで使われています。
UIUCTF 2021 Baby python fixed
https://jgeralnik.github.io/writeups/2021/08/09/baby-python-fixed/
UIUCTF 2022 A Horse with No Names / A Horse with No Neighs
中国語のブログ
他にも非想定解に使われたものなどいろいろ
code golf
調べていて見つけたWriteup(polygl0ts)で面白い悪用方法があったので紹介します。
b01ler CTF 2021 の pyjailgolf3 という問題です。
from collections.abc import __builtins__ bi = __builtins__ del bi["help"] line = input(">>> ") flag = "THIS_IS_FLAG" if len(line) > 10: raise Exception() try: eval(line, {"__builtins__": bi}, locals()) except: pass
b01lers-ctf-2021/pyjailgolf3.py at main · b01lers/b01lers-ctf-2021 · GitHub
10文字以内、help関数なしという制限のpyjailです。変数にフラグが格納されているのでprint(flag)
をしたいところですが、残念ながらこれは11文字です。
想定解では、NFKCの挙動を使います。ligature(合字)をNFKCで正規化すると複数の文字に分解されます。(例:「㍍」→「メートル」) そして、Unicodeにはfl
というfとlの合字が存在します。これを使えばflag
のfl
を一文字に短縮することができます。
つまり、print(flag)
が正解です。シンプルですが、競技中にこの解法を閃くのはかなり難しいと思います。
余談ですが、合字テクが使える名前の関数があるなら一般的なコードゴルフでも短縮が狙えるかもしれませんね。あるかは調べてませんが……