注意:从 python 2.7.4 开始,这对于 ZIP 存档不存在问题。答案底部的详细信息。此答案侧重于 tar 档案。
要找出路径真正指向的位置,请使用os.path.abspath()(但请注意有关将符号链接作为路径组件的警告)。如果您使用 abspath 规范化 zipfile 中的路径,并且它确实 not 包含当前目录作为前缀,则它指向它之外。
但您还需要检查从存档中提取的任何符号链接的 值(tarfile 和 unix zipfile 都可以存储符号链接)。如果您担心众所周知的“恶意用户”会故意绕过您的安全性,而不是简单地将自身安装在系统库中的应用程序,这一点很重要。
这就是前面提到的警告:如果您的沙箱已经包含指向目录的符号链接,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 共享点和前导(反)斜杠将被剥离,例如:///foo/bar 在 Unix 上变为 foo/bar,而 @ 987654334@ 在 Windows 上变为 foo\bar。并且成员文件名中的所有".." 组件都将被删除,例如:../../foo../../ba..r 变为foo../ba..r。在 Windows 上,非法字符(:、<、>、|、"、? 和 *)[被] 替换为下划线 (_)。
tarfile 类还没有经过类似的清理,所以上述答案仍然适用。