安全使用Python解压ZIP或TAR(Safely extract zip or tar usin

2019-07-28 20:50发布

我试图提取用户提交的ZIP和TAR文件解压缩到一个目录。 为zip文件的文档extractall方法(同样用tar文件的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在这种情况下? 如果是的话,我可以保证什么样的方式,文件将永远不会拉闸目标目录之外?

Answer 1:

注:与Python 2.7.4开始,这是一个非问题的ZIP压缩文件。 在回答底部的细节。 这个答案集中在tar归档。

为了找出其中的路径的确表明,使用os.path.abspath()但要注意对符号链接的告诫为路径组件)。 如果您与您的zip文件规范的路径abspath ,它包含当前目录作为前缀,它的指向外面。

但是,你还需要检查从档案中提取的任何符号链接的 (两者tarfiles和UNIX zipfiles可以存储符号连接)。 如果你担心一个众所周知的“恶意用户”,将有意地绕过您的安全,而不是简单地将自己安装在系统库的应用程序,这是非常重要的。

这就是前面提到的警告: abspath会被误导,如果你的沙箱已经包含一个指向目录的符号连接。 即使在沙箱中指向一个符号链接可能是危险的:符号连接sandbox/subdir/foo -> ..点到sandbox ,这样的路径sandbox/subdir/foo/../.bashrc应该被禁止。 这样做最简单的方法是等到以前的文件已提取和使用os.path.realpath() 幸运的extractall()接受一个发电机,所以这是很容易做到。

既然你问到的代码,这里有点那个explicates的算法。 它不仅禁止文件的沙箱之外的位置提取(这是被请求的是什么),而且还指向沙箱之外的位置沙箱内链接的创建。 我很好奇听到的话,任何人都可以潜入任何杂散的文件或链接过去吧。

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在Unix,和C:\foo\bar变得foo\bar上的Windows。 和所有的".."中一员的文件名组件将被移除,例如: ../../foo../../ba..r成为foo../ba..r 。 在Windows中,非法字符( :<>|"?* )[被]用下划线(_)代替。

tarfile类没有被同样消毒,所以上面的回答仍然apllies。



Answer 2:

使用ZipFile.infolist() / TarFile.next() / TarFile.getmembers()以获取有关档案的每个条目的信息,规范的路径,打开文件自己,用ZipFile.open() / TarFile.extractfile()得到一个类似文件的条目,并自己复制的一个数据。



Answer 3:

zip文件复制到一个空目录。 然后使用os.chroot ,使该目录的根目录。 然后解压缩在那里。

另外,您也可以拨打unzip与本身-j标志,而忽略了目录:

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


Answer 4:

相反,流行的答案,安全解压文件并没有完全解决像Python 2.7.4的。 该extractall方法仍然是危险的,并可能导致路径遍历,直接或通过符号链接的解链。 这是我应避免在Python的所有版本都攻击,甚至之前的Python 2.7.4,其中提取方法是易受攻击版本最终的解决方案:

import zipfile, os

def safe_unzip(zip_file, extractpath='.'):
    with zipfile.ZipFile(zip_file, 'r') as zf:
        for member in zf.infolist():
            abspath = os.path.abspath(os.path.join(extractpath, member.filename))
            if abspath.startswith(os.path.abspath(extractpath)):
                zf.extract(member, extractpath)

编辑:固定变量名冲突。 多亏了Juuso Ohtonen。



文章来源: Safely extract zip or tar using Python