我正在尝试将用户提交的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之外?如果是这样,我如何确保文件永远不会在目标目录之外结束?

最佳答案

注意:从python 2.7.4开始,这不是ZIP存档的问题。详细信息位于答案的底部。这个答案集中在tar文件上。

要确定路径真正指向的位置,请使用os.path.abspath()(但请注意有关符号链接(symbolic link)作为路径组件的警告)。如果您使用abspath标准化了zip文件中的路径,并且该路径不包含当前目录作为前缀,则该路径指向其外部。

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

这就是前面提到的警告:如果您的沙箱已经包含指向目录的符号链接(symbolic link),则abspath将被误导。即使是指向沙箱中的符号链接(symbolic link)也可能很危险:symlink 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() 方法禁止在沙箱外部创建文件:


tarfile类尚未进行同样的清理,因此上述答案仍然适用。

09-30 17:45
查看更多