The English documentation is placed below the Chinese version.
在Python中,所有类都有一个基本无用的__subclasses__()
方法,这个实现不仅有一定内存开销,还使得代码变得不再安全,用户可以通过object.__subclasses__()
访问任何内置函数和类,使得彻底安全的exec
、eval
函数不复存在。
no-subclasses
库是一个清除所有类的__subclasses__()
列表的库,既减小了Python的内存开销,也实现了几乎彻底安全的exec
和eval
函数,阻止了exec
和eval
中的所有__subclasses__
攻击。
Python的__subclasses__()
默认几乎可以包含任何的类。启用no_subclasses
库后,__subclasses__()
总是会返回空列表,即使定义了新的类,也不例外。
>>> import no_subclasses
>>> len(object.__subclasses__()) # 不启用no_subclasses库时
313
>>> object.__subclasses__()[:5]
[<class 'type'>, <class 'async_generator'>, <class 'bytearray_iterator'>, <class 'bytearray'>, <class 'bytes_iterator'>]
>>>
>>> no_subclasses.init() # 启用no_subclasses库
>>> object.__subclasses__()
[]
>>> int.__subclasses__()
[]
>>> type.__subclasses__(type)
[]
另外,库提供了安全的exec
和eval
函数,函数中不能调用任何内置函数和类,也不能通过调用任何类的__subclasses__()
实现攻击。
>>> from no_subclasses import init,safe_eval
>>>
>>> safe_scope = {"__builtins__":{}} # 不能调用任何内置函数
>>> attack_expr = "(1).__class__.__base__.__subclasses__()"
>>> eval(attack_expr,safe_scope) # 启用no_subclasses之前的eval
[<class 'type'>, <class 'async_generator'>, <class 'int'>,
<class 'bytearray_iterator'>, <class 'bytearray'>,
<class 'bytes_iterator'>, <class 'bytes'>,
<class 'PyCapsule'>,<class 'classmethod'>,...] # 包含大量的内置函数,不安全
>>>
>>> init()
>>> safe_eval(attack_expr) # 或eval(attack_expr,safe_scope)
[]
-
hack_class(cls)
: 清除一个类的__subclasses__()
列表。 -
hack_all_classes(start = object, ignored=())
: 从一个根类(默认为object
)开始,清除所有子类的__subclasses__
列表。ignored
为一个列表或元组,表示要忽略的类,默认为空。 -
init_build_class_hook()
: 修改内置的__build_class__
函数,使得执行class
语句时不会再次修改__subclasses__()
列表,不需要重新调用hack_class()
。 -
init_type_hook()
: 修改内置的type()
和type.__new__()
,使得调用type()
乃至type.__new__()
时不会再次修改__subclasses__()
,不需要重新调用hack_class()
。(需要pydetour
库) -
init()
: 初始化整个no_subclasses
库,一并调用hack_all_classes
、init_build_class_hook
和init_type_hook
。(推荐使用)
hack_class
方法基于更底层的pyobject
库的get_type_subclasses
和set_type_subclasses
方法实现,
而hack_all_classes
通过用BFS深入查找子类,再对每个类调用hack_class
实现。
In Python, all classes have a largely useless __subclasses__()
method. This implementation not only incurs some memory overhead but also makes the code less secure. Users can access any built-in functions and classes through object.__subclasses__()
, which undermines the complete security of exec
and eval
functions.
The no-subclasses
library is designed to remove the __subclasses__()
method from all classes, thereby reducing Python's memory overhead and achieving almost complete security for exec
and eval
functions, preventing all __subclasses__
attacks within exec
and eval
.
By default, Python's __subclasses__()
can include almost any class. After enabling the no_subclasses
library, __subclasses__()
will always return an empty list, even if new classes are defined.
>>> import no_subclasses
>>> len(object.__subclasses__()) # Without no_subclasses library
313
>>> object.__subclasses__()[:5]
[<class 'type'>, <class 'async_generator'>, <class 'bytearray_iterator'>, <class 'bytearray'>, <class 'bytes_iterator'>]
>>>
>>> no_subclasses.init() # Enable no_subclasses library
>>> object.__subclasses__()
[]
>>> int.__subclasses__()
[]
>>> type.__subclasses__(type)
[]
Additionally, the library provides secure exec
and eval
functions, which cannot call any built-in functions or classes, nor can they be exploited by calling any class's __subclasses__()
method.
>>> from no_subclasses import init,safe_eval
>>>
>>> safe_scope = {"__builtins__":{}} # Cannot call any built-in functions
>>> attack_expr = "(1).__class__.__base__.__subclasses__()"
>>> eval(attack_expr,safe_scope) # eval before enabling no_subclasses
[<class 'type'>, <class 'async_generator'>, <class 'int'>,
<class 'bytearray_iterator'>, <class 'bytearray'>,
<class 'bytes_iterator'>, <class 'bytes'>,
<class 'PyCapsule'>,<class 'classmethod'>,...] # Contains many built-in functions, insecure
>>>
>>> init()
>>> safe_eval(attack_expr) # or eval(attack_expr,safe_scope)
[]
-
hack_class(cls)
: Clears the__subclasses__()
list of a class. -
hack_all_classes(start = object, ignored=())
: Starting from a root class (default isobject
), clears the__subclasses__
list of all subclasses.ignored
is a list or tuple of classes to be ignored, default is empty. -
init_build_class_hook()
: Modifies the built-in__build_class__
function so that executing aclass
statement does not modify the__subclasses__()
list again, eliminating the need to re-callhack_class()
. -
init_type_hook()
: Modifies the built-intype()
andtype.__new__()
so that callingtype()
ortype.__new__()
does not modify the__subclasses__()
list again, eliminating the need to re-callhack_class()
. (Requirespydetour
library) -
init()
: Initializes the entireno_subclasses
library, callinghack_all_classes
,init_build_class_hook
, andinit_type_hook
together. (Recommended)
The hack_class
method is implemented based on the lower-level pyobject
library's get_type_subclasses
and set_type_subclasses
methods. The hack_all_classes
method uses BFS to deeply search all subclasses and then calls hack_class
on each class.