使用Python安全提取zip或tar

2024-04-18 04:30:49 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试将用户提交的zip和tar文件解压到一个目录中。zipfile的extractall方法(类似于tarfile的extractall)的文档说明,路径可能是绝对的,也可能包含目标路径之外的..路径。相反,我可以自己使用extract,比如:

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之外结束?如果是,我如何才能确保文件永远不会出现在目标目录之外?


Tags: 文件path方法用户路径目录extractsome
3条回答

将zipfile复制到空目录。然后使用os.chroot使该目录成为根目录。然后打开拉链。

或者,可以使用-j标志调用unzip本身,该标志忽略目录:

import subprocess
filename = '/some/file.zip'
rv = subprocess.call(['unzip', '-j', filename])

注意:从Python2.7.4开始,这对于ZIP存档是不存在问题的。答案底部的细节。这个答案的重点是tar档案。

要找出路径真正指向的位置,请使用os.path.abspath()(但请注意符号链接作为路径组件的注意事项)。如果用abspath规范化来自zipfile的路径,并且它确实不包含当前目录作为前缀,那么它指向它的外部。

但您还需要检查从存档中提取的任何符号链接的(tarfiles和unix zipfiles都可以存储符号链接)。如果您担心一个众所周知的“恶意用户”会故意绕过您的安全性,而不是一个只在系统库中安装自身的应用程序,那么这一点非常重要。

这就是前面提到的警告:如果沙盒已经包含指向目录的符号链接,那么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存档不存在此问题:方法^{}禁止在沙盒之外创建文件:

Note: If a member filename is an absolute path, a drive/UNC sharepoint and leading (back)slashes will be stripped, e.g.: ///foo/bar becomes foo/bar on Unix, and C:\foo\bar becomes foo\bar on Windows. And all ".." components in a member filename will be removed, e.g.: ../../foo../../ba..r becomes foo../ba..r. On Windows, illegal characters (:, <, >, |, ", ?, and *) [are] replaced by underscore (_).

tarfile类没有经过类似的清理,因此上面的答案仍然适用。

使用ZipFile.infolist()/TarFile.next()/TarFile.getmembers()获取有关存档中每个条目的信息,规范化路径,自己打开文件,使用ZipFile.open()/TarFile.extractfile()获取与条目类似的文件,然后自己复制条目数据。

相关问题 更多 >