前言
最近事情太多,总是忙忙碌碌。项目、毕设、论文、学习…..
在宇哥引导下,简单复现了tendermint的一个DoS漏洞
漏洞简介
tendermint是Go语言编写的能够在多机器上安全一致地复制应用的拜占庭容错中间件,是跨链Cosmos项目的核心技术,用于各种分布式场景。
该软件的受影响版本容易受到拒绝服务(DoS)攻击。Go语言的http客户端在默认情况下支持解压gzip压缩的http响应,而tendermint的cli和light并未显式地设置http.transport对象的DisableCompression为false来禁用此支持。当它向攻击者控制的gzip DoS服务器发起请求时,将发生拒绝服务导致程序Crash。
影响版本
version < v0.31.1
tendermint在v0.31.1版本的http_client.go新增了以下代码来显示地禁用对gzip响应的解压

漏洞简析
漏洞产生的根本原因在于默认情况下Go的http.transport对象的DisableCompression属性为false,即支持gzip压缩的http响应,当go处理该请求的内容时会解压响应数据

而该默认支持容易受到gzip-bomb DoS攻击
gzip压缩方式可以高效得压缩重复数据,攻击者可将10GB的数据压缩至10MB并将其作为gzip编码的http响应
Go默认的http客户端处理该gzip响应时,10MB数据将解压扩至10GB,从而导致客户端OOMcrash

复现过程
环境
Attacker:gzip-bomb-dos.php on VPS
Victim:Ubuntu虚拟机 2g内存
过程
由于该漏洞的根本原因是Go的默认机制,这里复现直接采用Go模拟代替
Go编写存在gzip-bomb DoS风险的程序
在云服务器上制作一个10M的10G gzip压缩包,并用PHP实现gzip-bomb DoS响应

编译后在2g内存的Ubuntu虚拟机中执行,程序crash

细节
复现过程并不顺利,涉及Go语言的一个小细节,与漏洞本身无关
问题关键在于触发Go http客户端对返回的gzip压缩数据的解压
通过源码可以发现Go客户端对响应的gzip压缩数据的解压处理是在/src/net/http/transport.go中的persistConn对象的readLoop方法中实现的

Go http包中的transport作用和运行流程如下:
tranport用来建立一个连接,其中维护了一个空闲连接池
idleConn map[connectMethodKey][]*persistConn,其中的每个成员都是一个persistConn对象,persistConn是个具体的连接实例,包含了连接的上下文,会启动两个groutine分别执行readLoop和writeLoop, 每当transport调用roundTrip的时候,就会从连接池中选择一个空闲的persistConn,然后调用其roundTrip方法,将读写请求通过channel分别发送到readLoop和writeLoop中,然后会进行select各个channel的信息,包括连接关闭,请求超时,writeLoop出错, readLoop返回读取结果等。
所以只有当http客户端对请求响应数据的Body有【读】操作的时候才会触发gzip解压
防护方案
升级tendermint至v0.31.1以上
参考
https://github.com/tendermint/tendermint/pull/3430
https://github.com/tendermint/tendermint/commit/03085c2da23b179c4a51f59a03cb40aa4e85a613/