Skip to content

A library that removes the __subclasses__() list from all classes, allowing for nearly absolute security in exec and eval functions. 一个清除所有类的__subclasses__()列表的库,使得exec和eval函数变得几乎绝对安全。

License

Notifications You must be signed in to change notification settings

qfcy/no-subclasses

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Stars GitHub release License: MIT

The English documentation is placed below the Chinese version.

在Python中,所有类都有一个基本无用的__subclasses__()方法,这个实现不仅有一定内存开销,还使得代码变得不再安全,用户可以通过object.__subclasses__()访问任何内置函数和类,使得彻底安全的execeval函数不复存在。
no-subclasses库是一个清除所有类的__subclasses__()列表的库,既减小了Python的内存开销,也实现了几乎彻底安全execeval函数,阻止了execeval中的所有__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)
[]

另外,库提供了安全的execeval函数,函数中不能调用任何内置函数和类,也不能通过调用任何类的__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_classesinit_build_class_hookinit_type_hook(推荐使用)

实现原理

hack_class方法基于更底层的pyobject库的get_type_subclassesset_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.

Usage Example

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)
[]

Detailed Usage

  • hack_class(cls): Clears the __subclasses__() list of a class.

  • hack_all_classes(start = object, ignored=()): Starting from a root class (default is object), 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 a class statement does not modify the __subclasses__() list again, eliminating the need to re-call hack_class().

  • init_type_hook(): Modifies the built-in type() and type.__new__() so that calling type() or type.__new__() does not modify the __subclasses__() list again, eliminating the need to re-call hack_class(). (Requires pydetour library)

  • init(): Initializes the entire no_subclasses library, calling hack_all_classes, init_build_class_hook, and init_type_hook together. (Recommended)

Implementation Principle

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.

About

A library that removes the __subclasses__() list from all classes, allowing for nearly absolute security in exec and eval functions. 一个清除所有类的__subclasses__()列表的库,使得exec和eval函数变得几乎绝对安全。

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages