注意: 从python 2.7.4开始,这对于ZIP档案不再是一个问题。详情请见答案底部。本回答重点讨论tar档案。
要确定路径指向的真实位置,请使用os.path.abspath()
(但注意符号链接作为路径组件的限制)。如果你使用abspath
来规范化你的zip文件中的路径,并且它不包含当前目录作为前缀,则表示它指向外部。
但是,您还需要检查从您的存档中提取的任何符号链接的值(tar文件和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):
return not resolved(joinpath(base,path)).startswith(base)
def badlink(info, base):
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: Symlink to", finfo.linkname
elif finfo.islnk() and badlink(finfo,base):
print >>stderr, finfo.name, "is blocked: Hard link 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共享点和前导(反)斜杠,例如:在Unix上,///foo/bar
变成 foo/bar
,在Windows上,C:\foo\bar
变成 foo\bar
。所有成员文件名中的 ".." 组件将被删除,例如:../../foo../../ba..r
变成 foo../ba..r
。在Windows上,非法字符(:
、<
、>
、|
、"
、?
和*
)将被替换为下划线(_)。
tarfile
类没有进行类似的清理,因此上述答案仍然适用。
zipfile.extract()
方法禁止在沙盒之外创建文件。因此,自Python 2.7.4起,该方法是安全的。然而,tar档案仍存在漏洞。 - alexis