如何保护服务免受 gzip 炸弹的攻击?
How to protect service from gzip bomb?
我有 test.gzip
个文件 json
{"events": [
{"uuid":"56c1718c-8eb3-11e9-8157-e4b97a2c93d3",
"timestamp":"2019-06-14 14:47:31 +0000",
"number":732,
"user": {"full_name":"0"*1024*1024*1024}}]}
full_name 文件包含 1GB 的 0
,压缩文件大小 ~1Mb
如何在解包时保护我的服务,让我的记忆不至于结束?
func ReadGzFile(filename string) ([]byte, error) {
fi, err := os.Open(filename)
if err != nil {
return nil, err
}
defer fi.Close()
fz, err := gzip.NewReader(fi)
if err != nil {
return nil, err
}
defer fz.Close()
s, err := ioutil.ReadAll(fz)
if err != nil {
return nil, err
}
return s, nil
}
func main() {
b, err := ReadGzFile("test.gzip")
if err != nil {
log.Println(err)
}
var dat map[string]interface{}
if err := json.Unmarshal(b, &dat); err != nil {
panic(err)
}
fmt.Println(dat)
}
在这种情况下输出可以通过 OOMKiller 终止我的服务
可能具有欺骗性的是,压缩后的大小可能明显小于允许的大小(您可以或希望处理的大小)。在您的示例中,输入约为 1 MB,而未压缩的大小约为 1 GB。
在读取未压缩数据时,您应该在达到合理限制后停止。为了轻松做到这一点,您可以使用 io.LimitReader()
来指定您希望读取的最大字节数。是的,您必须包装 unzipped 流,而不是原始的压缩流。
这是一个示例:
limited := io.LimitReader(fz, 2*1024*1024)
s, err := ioutil.ReadAll(limited)
以上示例将可读数据限制为 2 MB。当解压缩的数据多于此时会发生什么? io.LimitReader()
返回的 io.Reader
(顺便说一下 io.LimitedReader
)将报告 io.EOF
。这可以保护您的服务器免受攻击,但可能不是处理攻击的最佳方式。
既然你提到这是为了休息 API,更合适的解决方案是类似的 http.MaxBytesReader()
。这将传递的 reader 包装起来以读取直到给定限制,如果达到该限制,它会 returns 一个错误,并将错误发送回 HTTP 客户端,并关闭底层 read-closer。如果 http.MaxBytesReader()
的默认行为不适合你,检查它的来源,复制它并修改它,它相对简单。根据您的需要进行调整。
另请注意,您不应将所有内容(未压缩的数据)读入内存。您可以将 "limited reader" 传递给 json.NewDecoder()
,后者将在解码输入 JSON 时从给定的 reader 读取。当然如果passed limitedreader报错,解码就会失败
不要将所有内容都读入内存。如果可能,对流进行操作。在您的示例中,这是 100% 的可能性:
func ReadGzFile(filename string) (io.ReadCloser, error) {
fi, err := os.Open(filename)
if err != nil {
return nil, err
}
return gzip.NewReader(fi)
}
func main() {
b, err := ReadGzFile("test.gzip")
if err != nil {
log.Println(err)
}
defer b.Close()
var dat map[string]interface{}
if err := json.NewDecoder(b).Decode(&dat); err != nil {
panic(err)
}
fmt.Println(dat)
}
此 Decode
方法具有忽略流中第一个有效 JSON 对象之后的任何垃圾的副作用(可能需要也可能不需要)。就您而言,这似乎是一个好处。在某些情况下,它可能不是。
我有 test.gzip
个文件 json
{"events": [
{"uuid":"56c1718c-8eb3-11e9-8157-e4b97a2c93d3",
"timestamp":"2019-06-14 14:47:31 +0000",
"number":732,
"user": {"full_name":"0"*1024*1024*1024}}]}
full_name 文件包含 1GB 的 0
,压缩文件大小 ~1Mb
如何在解包时保护我的服务,让我的记忆不至于结束?
func ReadGzFile(filename string) ([]byte, error) {
fi, err := os.Open(filename)
if err != nil {
return nil, err
}
defer fi.Close()
fz, err := gzip.NewReader(fi)
if err != nil {
return nil, err
}
defer fz.Close()
s, err := ioutil.ReadAll(fz)
if err != nil {
return nil, err
}
return s, nil
}
func main() {
b, err := ReadGzFile("test.gzip")
if err != nil {
log.Println(err)
}
var dat map[string]interface{}
if err := json.Unmarshal(b, &dat); err != nil {
panic(err)
}
fmt.Println(dat)
}
在这种情况下输出可以通过 OOMKiller 终止我的服务
可能具有欺骗性的是,压缩后的大小可能明显小于允许的大小(您可以或希望处理的大小)。在您的示例中,输入约为 1 MB,而未压缩的大小约为 1 GB。
在读取未压缩数据时,您应该在达到合理限制后停止。为了轻松做到这一点,您可以使用 io.LimitReader()
来指定您希望读取的最大字节数。是的,您必须包装 unzipped 流,而不是原始的压缩流。
这是一个示例:
limited := io.LimitReader(fz, 2*1024*1024)
s, err := ioutil.ReadAll(limited)
以上示例将可读数据限制为 2 MB。当解压缩的数据多于此时会发生什么? io.LimitReader()
返回的 io.Reader
(顺便说一下 io.LimitedReader
)将报告 io.EOF
。这可以保护您的服务器免受攻击,但可能不是处理攻击的最佳方式。
既然你提到这是为了休息 API,更合适的解决方案是类似的 http.MaxBytesReader()
。这将传递的 reader 包装起来以读取直到给定限制,如果达到该限制,它会 returns 一个错误,并将错误发送回 HTTP 客户端,并关闭底层 read-closer。如果 http.MaxBytesReader()
的默认行为不适合你,检查它的来源,复制它并修改它,它相对简单。根据您的需要进行调整。
另请注意,您不应将所有内容(未压缩的数据)读入内存。您可以将 "limited reader" 传递给 json.NewDecoder()
,后者将在解码输入 JSON 时从给定的 reader 读取。当然如果passed limitedreader报错,解码就会失败
不要将所有内容都读入内存。如果可能,对流进行操作。在您的示例中,这是 100% 的可能性:
func ReadGzFile(filename string) (io.ReadCloser, error) {
fi, err := os.Open(filename)
if err != nil {
return nil, err
}
return gzip.NewReader(fi)
}
func main() {
b, err := ReadGzFile("test.gzip")
if err != nil {
log.Println(err)
}
defer b.Close()
var dat map[string]interface{}
if err := json.NewDecoder(b).Decode(&dat); err != nil {
panic(err)
}
fmt.Println(dat)
}
此 Decode
方法具有忽略流中第一个有效 JSON 对象之后的任何垃圾的副作用(可能需要也可能不需要)。就您而言,这似乎是一个好处。在某些情况下,它可能不是。