文件解压之过(Zip Slip)漏洞导致 Python 代码执行
Python 中负责解压压缩文件的代码实现上并不安全,存在目录遍历漏洞,攻击者可以利用该漏洞覆盖**__init__.py**文件,实现任意代码执行。
在PHP中,实现代码执行最为简单的一种方式就是利用PHP中不安全的文件上传处理逻辑。如果你可以欺骗文件上传逻辑,上传任意PHP文件,那么你就可以执行任意PHP代码。
然而,如果我们面对的是使用Go、Node.js、Python、Ruby等编写的现代Web框架时,即使我们把.py或者.js文件成功上传到服务器上,通过URL请求这些文件通常并不会返回任何结果,即使我们可以通过URL来访问这些资源,也不会触发任何代码执行动作
但是我们可以通过构造压缩包实现代码执行,精心构造的压缩文件虽然看起来人畜无害,但如果负责解压此类文件的代码本身并不安全,那么这种文件就会带来安全风险。
首先我们来了解一下 Zip Slip 漏洞
ZIP 路径穿越漏洞
许多应用会从用户上传的 ZIP 压缩包中解压文件。但 ZIP 文件内可以包含 伪造路径,如:
1 | ../../../../tmp/evil.py |
如果程序在解压时不验证路径安全性,就可能将这些文件 写到任意路径 —— 例如写到 ~/.bashrc、/var/www/html/、应用目录、配置目录等
这称为 Zip Slip(路径穿越)漏洞
→ 当恶意 ZIP 中包含 Python 文件、shell 脚本、配置文件等时,就可能被不慎执行,从而导致 远程代码执行(RCE)
命令执行
示例代码:
1 | def unzip(zip_file, extraction_path): |
这段 python 代码非常简单,可以解压 zip 文件并返回归档文件中包含的文件列表。文件上传操作结束后,服务器会收到 zip 文件,然后将 zip 文件发送给 unzip() 进行解压。
1 | outfile = os.path.join(extraction_path, filename) |
通过这一句可以看出来用户可以控制其中的 filename 变量,如果我们把 filename 的值设为../../foo.py,运行结果如下
1 | >>> import os |
利用路径遍历漏洞,我们成功将文件写入了/home/kali目录下
而实现代码执行,我们需要利用的是 Python 中的__init__.py
Python 官方文档:
如果某个目录想成为Python中的包,那么该目录中就需要包含__init__.py文件,这样就能避免模块搜索时把目录名为常用字符串(如string)的那些目录包含进来。在最简单的情况下,init.py可以是个空文件,也可以用来执行包中的初始化代码或者设置__all__变量,稍后会继续描述。
假设 Web 应用将某个目录当成 Python 包,如果我们使用任意 Python 代码覆盖该目录中的 init.py 文件,当目标应用导入这个包时,就会执行我们的代码。通常为了顺利执行代码,需要重启服务器,在实战中一般只能被动等待蓝队进行重启操作,但是如果是启用了 dubug 功能的 flask 服务器,只要 Python 文件发生改动,服务器就会重启
示例 Payload
1 | import zipfile |
因为主功能文件 server.py 会从 config 目录中导入 settings.py 文件,这意味着如果我们可以将代码写入到config/__init__.py,所以我们最终构造了一个这样的 Payload
z_info.external_attr = 0777 << 16L这条语句会将文件权限设为所有人可读可写权限
上传成功后,Flask 应用就会开始重载,然后服务器上的控制台会打印出“test”字符串
实战中的利用
一个问题就是如果不是 debug 模式下的 flask 服务器,我们要一直等待服务器重启,另一个问题是我们不能每次都知道目标应用使用的包的具体路径,如果目标使用的是开源项目,我们能很轻松的获取其目录信息;如果是闭源应用,我们可以猜测比较常见的包目录,如conf、config、settings、utils、urls、view、tests、scripts、controllers、modules、models、admin、login等。这些包目录经常出现在某些Python Web框架中,如Django、Flask、Pyramid、Tornado、CherryPy、web2py等
换个思路,如果目标 Web 应用运行在 Ubuntu Linux 之中,这时内置的Python包位于/home/<user>/.local/lib/python2.7/site-packages/pip目录中
假设目标应用运行在用户目录中,那么我们就可以构造类似../../.local/lib/python2.7/site-packages/pip/__init__.py之类的文件名
如果目标应用使用的是 virtualenv,假设 virtualenv 的目录为 venv,那么我们就可以使用类似../venv/lib/python2.7/site-packages/pip/__init__.py之类的文件名
这样处理后 pip 会受到影响,当下次服务器运行 pip 时就会执行我们的代码
漏洞预防
为了防御这个漏洞,你需要使用ZipFile.extract()来解压文件
理论上也可以手动检查目录是否违法,但是太麻烦了,不如用ZipFile.extract()
如果待处理文件使用的是绝对路径,那么路径中包含的驱动、UNC字符以及前缀(后缀)斜杠会被过滤掉,例如,在Unix上,///foo/bar经过处理后会变为foo/bar,在Windows上,C:foobar经过处理后会变为foobar。文件名中包含的所有“..”字符会被移除,例如,../../foo../../ba..r会变成foo../ba..r。在Windows上的非法字符(:、<、>、|、”、?、以及*)会被替换为下划线(_)







