ZIP解压漏洞

  • 漏洞场景

  • 解压出的标准化路径文件在解压目标目录之外

  • 解压的文件消耗过多的系统资源

  • 攻击影响

  • 对于第1种情况:攻击者可以从 zip 文件中往用户可访问的任何目录写入任意的数据;

  • 对于第2种情况:当资源使用远远大于输入数据所使用的资源时,就可能产生拒绝服务。
    Zip 算法有非常高的压缩比。例如,一个由字符 a 和字符 b 交替出现的行构成的文件,压缩比可以达到200:1。使用针对目标压缩算法的输入数据,或者使用更多的输入数据(不针对目标压缩算法的),或者使用其他的压缩方法,甚至可以达到更高的压缩比。
    由于 Zip 算法有极高的压缩率,即使在解压如 ZIP GIF gzip 编码 HTTP 的小文件时,也可能会导致过度的资源消耗,导致 zip 炸弹(zip bomb)。

  • 防范措施

  • 任何被提取条目的目标路径不在程序预期目录之内时(必须先对文件名进行标准化),要么拒绝将其提取出来,要么将其提取到一个安全的位置。

  • Zip文件中任何被提取条目,若解压之后的文件大小超过一定的限制时,必须拒绝将其解压。具体大小限制由平台的处理性能来决定。

今天实验部分只演示了 跨目录解压 需要压缩包的条目足够多

第二种情况的 解压单个条目需要的资源足够大 ,不好弄,懒得试。

views部分

views 文件夹里新建一个 File ,命名为 ZipController.tpl ,添加如下代码(即在 body 标签里添加两个表单,各放一个 input 表示要上传的压缩包):

<div class="postform">
    <p> ZIP炸弹 </p>
    <form enctype="multipart/form-data" action="http://127.0.0.1:8080/problems/ZipBomb" method="post">
        <input type="file" name="uploadname" />
        <input type="submit">
    </form>
    <br><br><br><br>
    <p> ZIP炸弹防范 </p>
    <form enctype="multipart/form-data" action="http://127.0.0.1:8080/problems/SafeZipBomb" method="post">
        <input type="file" name="uploadname" />
        <input type="submit">
    </form>

controllers部分

controllers 文件夹里新建一个go文件,命名为ZipController.go ,添加如下代码(老惯例,仍然是声明了两个对比的控制器,并分别重写了GetPost函数):

package controllers
import (
	"fmt"
		"log"
		"github.com/astaxie/beego"
	"archive/zip"
	"path/filepath"
	"errors"
// ZIP炸弹
type ZipController struct {
	beego.Controller
func (c *ZipController) Get() {
	c.TplName = "ZipController.tpl"
*解压压缩包操作
* @zipfile 要解压的压缩包,必须是严格的zip类型,其他类型修改后缀强转为zip也不行!!!
* @destpath 解压到的目的路径
func unzipfile(zipfile, destpath string) bool {
	// 打开压缩包
	rc, err := zip.OpenReader(zipfile)
	// fmt.Println(zipfile)
	if err != nil {
		fmt.Println("Open the zipfile error.")
		return false
	defer rc.Close()
	// 遍历压缩包内的目录和文件
	for _, file := range rc.File {
		// 输出当前要解压的文件
		fmt.Printf("Contents of %s:\n", file.Name)
		// file此时可能是文件可能是目录,统一Open打开看看会不会报错。
		irc, err := file.Open()
		if err != nil {
			fmt.Println("open file which in zip archive error.")
			break
		defer irc.Close()
		// 拼装指定的解压路径
		var targetpath = filepath.Join(destpath, file.Name)
		// 如果是目录则创建;如果是文件则把文件复制到指定的目的地
		if file.FileInfo().IsDir() {
			os.MkdirAll(targetpath, file.Mode())/** 【错误】直接将文件路径传给MkdirAll(),缺乏事先的标准化**/
		} else {
			// 新建一个文件
			f, err := os.OpenFile(targetpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
			if err != nil {
				fmt.Println("open the dest file error.")
				break
			defer f.Close()
			// 从压缩流中把内容复制到目的文件中,从打开的irc到新建的f
			wt, err := io.Copy(f, irc) /** 【错误】未检查解压文件消耗情况  **/
			if err != nil {
				fmt.Println("copy file content error.")
				break
			// 输出字节数
			fmt.Println("wt:", wt)
	return true
// 上传压缩包的post请求处理
func (c *ZipController) Post() {
	c.TplName = "ZipController.tpl"
	if c.Ctx.Request.MultipartForm.File["uploadname"] == nil {
		fmt.Println("哈哈,我很健壮")
		return
	// 还是像上传文件一样上传压缩包,然后上传成功后立即解压至当前目录,
	// 获取控制器数据流里的压缩包,不限制压缩包类型
	f, h, err := c.GetFile("uploadname")
	if err != nil {
		log.Fatal("getfile err ", err)
	} else {
		// 保存位置在 static/upload, 没有文件夹要先创建,不然压缩包保存失败
		pathSrc := "static/upload/" + h.Filename
		// 正则匹配,\表转义
		pattern := `\\static\\upload\\`
		// 验证压缩包是否在安全路径下,防一手跨目录上传
		if !validate(pathSrc, pattern) {
			fmt.Println("file not in security directory.")
			return
		// 肯定是安全目录,所以先保存压缩包,再解压
		c.SaveToFile("uploadname", pathSrc)
		// 如果解压失败把压缩包删了。
		if !unzipfile(pathSrc, "static/upload/") {
			os.Remove(pathSrc)
	defer f.Close()
// ZIP炸弹防范
type SafeZipController struct {
	beego.Controller
func (c *SafeZipController) Get() {
	c.TplName = "ZipController.tpl"
const TOO_MANY_FILE int = 1024
// max size of unzipped data, 100MB
const TOOBIG = 0x6400000
const BUFSIZE = 1024
*解压压缩包操作
* @zipfile 要解压的压缩包
* @destpath 解压到的目的路径
在每解压一个条目之前都对目标文件路径进行校验,若校验不通过则跳过该条目,继续后面的解压,
除了校验文件路径之外,还会检查每一个条目的大小,如果条目太大(例如是100M)则会跳过该条目;
最后代码会计算压缩包中的总条目数量(例如超过1024个)则解压失败。
func safeunzipfile(zipfile, destpath string) bool {
	// 打开压缩包
	rc, err := zip.OpenReader(zipfile)
	if err != nil {
		fmt.Println("Open the zipfile error.")
		return false
	defer rc.Close()
	/**【修改】解压文件的数量超过1024限制 **/
	if len(rc.File) > TOO_MANY_FILE {
		fmt.Println("Too many file will be unzip.")
		return false
	// 这一步只是对压缩包的压缩目录进行标准化,事实是可能压缩包里的文件本身也带有../的前缀,等下拼接的时候也要标准化
	destpath = filepath.Clean(destpath) /**【修改】将目的路径标准化 **/
	// 遍历压缩包内的目录和文件
	for _, file := range rc.File {
		// 输出当前要解压的文件
		fmt.Printf("Contents of %s:\n", file.Name)
		// file此时可能是文件可能是目录,统一Open打开看看会不会报错。
		irc, err := file.Open()
		if err != nil {
			fmt.Println("open file which in zip archive error.")
			continue
		defer irc.Close()
		// 拼装指定的解压路径
		var targetpath = filepath.Join(destpath, file.Name)
		// 拼接后再进行一次校验,确保是解压至安全目录下
		// 正则匹配,\表转义
		pattern := `\\static\\upload\\`
		// 验证压缩包是否在安全路径下,防一手跨目录上传
		if !validate(targetpath, pattern) {
			fmt.Println("unzip file not in security directory.")
			continue
		// 如果是目录则创建;如果是文件则把文件复制到指定的目的地
		if file.FileInfo().IsDir() {
			os.MkdirAll(targetpath, file.Mode())
		} else {
			f, err := os.OpenFile(targetpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
			if err != nil {
				fmt.Println("open the dest file error.")
				continue
			defer f.Close()
			// 从压缩流中把内容复制到目的文件中
			wt, err := copyBuffer(f, irc) /**【修改】检查解压文件消耗情况 **/
			if err != nil {
				fmt.Println("copy file content error.")
				break
			fmt.Println("wt:", wt)
	return true
func copyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
	buf := make([]byte, BUFSIZE)
	for {
		nr, er := src.Read(buf)
		if nr > 0 {
			//判断文件大小是否超出限制
			if written > TOOBIG {
				err = errors.New("The file too big!")
				break
			nw, ew := dst.Write(buf[0:nr])
			if nw > 0 {
				written += int64(nw)
			if ew != nil {
				err = ew
				break
			if nr != nw {
				err = io.ErrShortWrite
				break
		// 文件读完,返回
		if er == io.EOF {
			break
		// 文件读出错,返回
		if er != nil {
			err = er
			break
	return written, err
func (c *SafeZipController) Post() {
	c.TplName = "ZipController.tpl"
	if c.Ctx.Request.MultipartForm.File["uploadname"] == nil {
		fmt.Println("哈哈,我很健壮")
		return
	// 还是像上传文件一样上传压缩包,然后上传成功后立即解压至当前目录,
	// 获取控制器数据流里的压缩包,不限制压缩包类型
	f, h, err := c.GetFile("uploadname")
	if err != nil {
		log.Fatal("getfile err ", err)
	} else {
		// 保存位置在 static/upload, 没有文件夹要先创建,不然压缩包保存失败
		pathSrc := "static/upload/" + h.Filename
		// 正则匹配,\表转义
		pattern := `\\static\\upload\\`
		// 验证压缩包是否在安全路径下,防一手跨目录上传
		if !validate(pathSrc, pattern) {
			fmt.Println("file not in security directory.")
			return
		// 肯定是安全目录,所以先保存压缩包,再解压
		c.SaveToFile("uploadname", pathSrc)
		// 如果解压失败把压缩包删了。
		if !safeunzipfile(pathSrc, "static/upload/") {
			os.Remove(pathSrc)
	defer f.Close()

routers部分

routers/router.go 文件添加如下代码(即为上述两个控制器注册路由):

// ZIP炸弹问题
beego.Router("/problems/ZipBomb", &controllers.ZipController{})
beego.Router("/problems/SafeZipBomb", &controllers.SafeZipController{})

这样,无论url是访问/problems/ZipBomb 还是/problems/SafeZipBomb,两种Get请求都能正确渲染ZipController.tpl这个页面,然后当从表单发送Post请求时,一个表单会发送至ZipControllerPost函数响应并处理,而另一个表单会发送至SafeZipControllerPost函数响应并处理。

在浏览器中输入http://127.0.0.1:8080/problems/ZipBomb
这里写图片描述

我这里准备了一个压缩包如图
这里写图片描述

在“ZIP炸弹”的表单里选择该压缩包并提交上传:
这里写图片描述

后台显示如下:
这里写图片描述

可以看到压缩包被成功上传并且解压。

跨目录解压

使用编辑器(如010 Editor)打开压缩包,搜索“HelloWorld”字样
这里写图片描述

在搜索到的两处地方,都要将“HelloWorld.exe”修改为“../loWorld.exe”,即将前面三个字符“Hel”修改为“../
这里写图片描述

保存后,打开压缩包一看,出现了,传说中的“../”:
这里写图片描述

在“ZIP炸弹”的表单里选择该压缩包并提交上传:
这里写图片描述

后台显示如下:
这里写图片描述

可以看到,由于“HelloWorld.exe”被修改为“../loWorld.exe”,因而被解压到上级目录去了(“../”代表上级目录)。

跨目录解压防范

在“ZIP炸弹防范”的表单里选择该压缩包并提交上传:
这里写图片描述

后台显示如下:
这里写图片描述

后台在解压“../loWorld.exe” 的时候,检测到该被解压的文件不在安全目录下,因而拒绝将其解压出来,可以看到upload目录内和目录外均没有“loWorld.exe”。

表单的本意设计是可以选择一个本机内的压缩包,将其上传至服务器的\static\upload 目录,并且将其解压至当前目录下。

然而在ZipControllerPost函数中,未对每一个被解压的文件名做校验,直接将文件解压路径传递给os.MkdirAll()函数,同时也未检查解压文件的数目、资源消耗情况,这可能会导致程序运行到本地资源被耗尽。

// 拼装指定的解压路径
var targetpath = filepath.Join(destpath, file.Name)
// 如果是目录则创建;如果是文件则把文件复制到指定的目的地
if file.FileInfo().IsDir() {
	os.MkdirAll(targetpath, file.Mode())/** 【错误】直接将文件路径传给MkdirAll(),缺乏事先的标准化**/
// 从压缩流中把内容复制到目的文件中,从打开的irc到新建的f
wt, err := io.Copy(f, irc) /** 【错误】未检查解压文件消耗情况  **/
if err != nil {
	fmt.Println("copy file content error.")
	break

虽然Windows系统并不允许正常文件名含有../的字符,但是先将文件放进压缩包中,再通过修改压缩包字节的方式修改包内文件名,仍然可以使得文件名含有../的字符。

所以当“HelloWorld.exe”被修改为“../loWorld.exe”后,解压的目录随之变成了static/upload/../loWorld.exe ,也就是“static/loWorld.exe

于是同上一节的跨目录上传一样,产生了压缩包的跨目录压缩漏洞,通过这个漏洞,攻击者同样可以将文件上传到任意目录。

推荐防范措施:
1) 在每解压一个条目之前都对文件路径进行校验,若校验不通过则跳过该条目,继续后面的解压;

// 拼装指定的解压路径
var targetpath = filepath.Join(destpath, file.Name)
// 拼接后再进行一次校验,确保是解压至安全目录下
// 正则匹配,\表转义
pattern := `\\static\\upload\\`
// 验证压缩包是否在安全路径下,防一手跨目录上传
if !validate(targetpath, pattern) {
	fmt.Println("unzip file not in security directory.")
	continue

校验的方式即为上一节的正则匹配,只要验证解压路径的子串是否含有安全目录即可

2) 检查每一个条目的大小,如果条目太大(例如是100M)则会跳过该条目

//判断文件大小是否超出限制
if written > TOOBIG {
	err = errors.New("The file too big!")
	break

3) 提前计算压缩包中的总条目数量,如果数量过大(例如超过1024个)则拒绝解压。

const TOO_MANY_FILE int = 1024
/**【修改】解压文件的数量超过1024限制 **/
if len(rc.File) > TOO_MANY_FILE {
	fmt.Println("Too many file will be unzip.")
	return false

比如这里,如果压缩包里的文件数目超过1024,则会出现如下输出并拒绝解压:
这里写图片描述

不好意思,"BeeGo框架实现的一个WEB应用实例"没有附带数据库,特此补正! 本应用是用BeeGo框架进行的一个WEB应用快速开发,是一个在生产环境实际运行的商业软件。 适用数据库MySQL,详细配置请参阅conf\global\app.ini。本应用打包部署工具为Bee。 前端采用了amazeui制作工作台页面,标准的左边菜单栏右边工作页面---一个适合各种此类应用的快速开发模板。其他还有ztree树形组件、echarts百度报表插件、paging分页组件。。 实现了几个单表的CRUD操作的几个模块,其中"子系统管理"模块探索了多表关联的复杂操作。并且为了实现模糊查询,试写了一个构造原生SQL进行分页查询的前后台组件,是进阶软件开发者不可多得的学习素材。 做为彩蛋,本作还用百度echarts报表插件实现了饼状图和柱状图两个报表。 欢迎各位Go语言爱好者加群学习讨论,BeeGo学习交流群 552015496。作者倾情奉献哦 (^_^)
Beego从0开始 Beego简介 ​ Beego是一个基于Go语言开发的web框架beego是一个快速开发Go应用的http框架,go 语言方面技术大牛。beego可以用来快速开发API、Web、后端服务等各种应用,是一个RESTFul的框架,主要设计灵感来源于tornado、sinatra、flask这三个框架,但是结合了Go本身的一些特性(interface、struct继承等)而设计的一个框架。 为什么使用Beego 爱国:Beego是一个由中国人开发的web框架,狂赞!66666666
1、专业不对口,你为什么选择软件测试 首先,面试官是为你对于“软件测试行业”的认知,其次是考核你对于未来职业发展方向的看法,如果你回答只是简单的这个行业简单、容易、工资高,这显然不是面试官想要得到的。你应该从一下几点回答:(仅供参考)  1、自身兴趣,喜欢软件测试工作,喜欢找BUG。   2、 行业发展,测试行业属于朝阳行业,可持续发展。   3 、缺口大,目前行业内人较少,按照黄金比例国内严重不足。   4 、未来发展,随着国内对于软件质量越来越重视,发展将非常广阔。   5、 职业寿命长,积累行业经验。
硬件系统 计算机硬件中的运算器主要功能是对数据和信息进行运算和加工。运算器包括以下几个部分:通用寄存器、状态寄存器、累加器和关键的算术逻辑单元。运算器可以进行算术计算(加减乘除)和逻辑运算(与或非)。 控制器和运算器共同组成了中央处理器(CPU)。控制器可以看作计算机的大脑和指挥中心,它通过整合分析相关的数据和信息,可以让计算机的各个组成部分有序地完成指令。 主存(内存)和辅存(外存),内存分为RAM和ROM, RAM是随机存储器,断电后数据丢失,决定一个计算机能运行多少应
最近有个需求,需要写个脚本,但要编译为exe可执行文件,首先考虑python打包,奈何使用pyinstaller打包后,出现各种各样的运行错误,最后放弃了,改为golang重写。因为要用到创建和解压zip文件,golang中使用zip模块的功能,远没有python那么方便。 一、压缩部分 把文件或者文件夹压缩为zip文件,主要过程就是创建目的zip文件,然后遍历源目录,将源目录下的文件拷贝到目的zip文件中,最重要的2个方法: 1、zip.newWriter 创建一个向zip文件中写入的writer.
文章目录Java编码安全数据校验规则1.1:校验信任边界传递的不可信数据规则1.2:禁止直接使用不可信数据来拼接SQL语句规则1.4:禁止直接使用不可信数据来记录数据规则1.6:验证路径前将其标准化规则1.7:安全的从ZipInputStream提取文件规则1.8:禁止未经验证的用户输入直接输出到HTML界面规则1.10: 禁止程序数据进行增、删、改、查时对客户端请求的数据过分相信而遗漏对于权限的判定规则1.11:敏感数据在信任域之间传递采用签名加密传输 Java编码安全 本文根据Java安全开发Che
Beego的简介 中国人自己开发的Go应用框架,支持八大低耦合独立的模块,同时支持bee小工具,快速开发Go的应用程序,另外还自带了监控模块,类似于SpringBoot的 Actutar的功能 优点是大而全,缺点是过于臃肿了,在某些模块,比如mvc模块、orm模块,不如一些小而专的框架(Gin) Orm框架 MVC路由 session管理 RestFul 配置文件...