[英]Safely extract zip or tar using Python
我正在尝试将用户提交的 zip 和 tar 文件提取到目录中。 zipfile 的extractall方法(与 tarfile 的extractall类似)的文档指出路径可能是绝对路径或包含目标路径之外的..
路径。 相反,我可以使用extract
myself,如下所示:
some_path = '/destination/path'
some_zip = '/some/file.zip'
zipf = zipfile.ZipFile(some_zip, mode='r')
for subfile in zipf.namelist():
zipf.extract(subfile, some_path)
这样安全吗? 在这种情况下,存档中的文件是否有可能在some_path
之外结束? 如果是这样,我可以通过什么方式确保文件永远不会在目标目录之外结束?
注意:从 python 2.7.4 开始,这不是 ZIP 档案的问题。 答案底部的详细信息。 这个答案侧重于 tar 档案。
要找出路径真正指向的位置,请使用os.path.abspath()
(但请注意有关符号链接作为路径组件的警告)。 如果您使用abspath
规范化 zipfile 中的路径并且它不包含当前目录作为前缀,则它指向它之外。
但是您还需要检查从存档中提取的任何符号链接的值(tarfile 和 unix zip 文件都可以存储符号链接)。 如果您担心众所周知的“恶意用户”会故意绕过您的安全,而不是简单地将自身安装在系统库中的应用程序,那么这一点很重要。
这是前面提到的警告:如果您的沙箱已经包含指向目录的符号链接, abspath
将被误导。 即使是指向沙箱内的符号链接也可能是危险的:符号链接sandbox/subdir/foo ->..
指向sandbox
,因此路径sandbox/subdir/foo/../.bashrc
应该被禁止。 最简单的方法是等到先前的文件被提取出来并使用os.path.realpath()
。 幸运的是extractall()
接受一个生成器,所以这很容易做到。
由于您要求提供代码,这里有一点可以解释算法。 它不仅禁止将文件提取到沙箱外的位置(这是所要求的),而且还禁止在沙箱内创建指向沙箱外位置的链接。 我很想知道是否有人可以偷偷通过它的任何杂散文件或链接。
import tarfile
from os.path import abspath, realpath, dirname, join as joinpath
from sys import stderr
resolved = lambda x: realpath(abspath(x))
def badpath(path, base):
# joinpath will ignore base if path is absolute
return not resolved(joinpath(base,path)).startswith(base)
def badlink(info, base):
# Links are interpreted relative to the directory containing the link
tip = resolved(joinpath(base, dirname(info.name)))
return badpath(info.linkname, base=tip)
def safemembers(members):
base = resolved(".")
for finfo in members:
if badpath(finfo.name, base):
print >>stderr, finfo.name, "is blocked (illegal path)"
elif finfo.issym() and badlink(finfo,base):
print >>stderr, finfo.name, "is blocked: Hard link to", finfo.linkname
elif finfo.islnk() and badlink(finfo,base):
print >>stderr, finfo.name, "is blocked: Symlink to", finfo.linkname
else:
yield finfo
ar = tarfile.open("testtar.tar")
ar.extractall(path="./sandbox", members=safemembers(ar))
ar.close()
编辑:从 python 2.7.4 开始,这对于 ZIP 档案来说不是问题:方法zipfile.extract()
禁止在沙箱外创建文件:
注意:如果成员文件名是绝对路径,驱动器/UNC sharepoint 和前导(反)斜杠将被去除,例如:
///foo/bar
foo/bar
,而C:\foo\bar
变为foo\bar
on Windows。成员文件名中的所有".."
组件将被删除,例如:../../foo../../../../foo../../ba..r
变为foo../ba..r
。 在 Windows 上,非法字符(:
、<
、>
、|
、"
?
和*
)[被] 替换为下划线 (_)。
tarfile
class 没有经过类似的清理,所以上面的答案仍然适用。
与流行的答案相反,从 Python 2.7.4 开始,安全解压缩文件并未完全解决。 extractall 方法仍然很危险,可能直接或通过符号链接的解压缩导致路径遍历。 这是我的最终解决方案,它应该可以防止 Python 的所有版本中的这两种攻击,甚至是提取方法易受攻击的 Python 2.7.4 之前的版本:
import zipfile, os
def safe_unzip(zip_file, extract_path='.'):
with zipfile.ZipFile(zip_file, 'r') as zf:
for member in zf.infolist():
file_path = os.path.realpath(os.path.join(extract_path, member.filename))
if file_path.startswith(os.path.realpath(extract_path)):
zf.extract(member, extract_path)
编辑 1:修复变量名冲突。 谢谢 Juuso Ohtonen。
编辑 2: s/abspath/realpath/g
。 谢谢蜥蜴
使用ZipFile.infolist()
/ TarFile.next()
/ TarFile.getmembers()
获取存档中每个条目的信息,归一化路径,自己打开文件,使用ZipFile.open()
/ TarFile.extractfile()
为条目获取类似文件的文件,然后自己复制条目数据。
将压缩文件复制到一个空目录。 然后使用os.chroot
使该目录成为根目录。 然后在那里解压。
或者,您可以使用-j
标志调用unzip
本身,它会忽略目录:
import subprocess
filename = '/some/file.zip'
rv = subprocess.call(['unzip', '-j', filename])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.