我试图提取用户提交的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
在这种情况下? 如果是的话,我可以保证什么样的方式,文件将永远不会拉闸目标目录之外?
注:与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。
使用ZipFile.infolist()
/ TarFile.next()
/ TarFile.getmembers()
以获取有关档案的每个条目的信息,规范的路径,打开文件自己,用ZipFile.open()
/ TarFile.extractfile()
得到一个类似文件的条目,并自己复制的一个数据。
zip文件复制到一个空目录。 然后使用os.chroot
,使该目录的根目录。 然后解压缩在那里。
另外,您也可以拨打unzip
与本身-j
标志,而忽略了目录:
import subprocess
filename = '/some/file.zip'
rv = subprocess.call(['unzip', '-j', filename])
相反,流行的答案,安全解压文件并没有完全解决像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。