[English | 中文]
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.