Apache Tomcat在9.0.37版本的更新中,修复了一个Websockt的拒绝服务漏洞
由于Websocket frame中的payload的长度没有正确验证,导致陷入了无限循环,造成拒绝服务。
Websocket 协议
RFC 6455中规定了Websocket frame的结构
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
前9位是一些标志位,接着7位为payload length,后面64位为扩展的payload length,payload len就是最后payload data的长度,对于扩展payload len,也做了详细的规定
Payload length: 7 bits, 7+16 bits, or 7+64 bits
The length of the "Payload data", in bytes: if 0-125, that is the
payload length. If 126, the following 2 bytes interpreted as a
16-bit unsigned integer are the payload length. If 127, thec
following 8 bytes interpreted as a 64-bit unsigned integer (the
most significant bit MUST be 0) are the payload length.
当payload len为0-127之间时,payload len就是payload data的长度,当payload len为126时,只使用后面的2个字节作为payload data的长度,当payload len为127时,则说明后面的8个字节是payload data长度,这时64位的Extended Payload Len最高位必须为0,代表无符号整数
漏洞分析
补丁
先看Apache Tomcat官方的更新commit
在org/apache/tomcat/websocket/WsFrameBase.java
中的processRemainingHeader
函数中,当payloadLength为127时,获取新的payloadLength,增加了非负的判断。
代码分析
这里使用Apache Tomcat9.0.35+JDK1.8.0_271进行调试,首先在补丁代码处下断点
使用tomcat自带的examples中的Websocket的接口ws://localhost:8080/examples/websocket/echoProgrammatic
,为了能让代码走到断点行,需要payload len为127,所以构造一个payload len为0b1111111
,payload data为test
的Websocket数据包发送
可以看到,inputBuffer
就是我们发送的整个数据包,此时取出了其中的payload len为127,接着我们进入byteArrayToLong
函数
这里start=2 len=8
,也就是正好取出了Websocket数据包的16位之后的64位数据,也就是Extended Payload Length进行计算,每次取低8位进行byte到long的转换,由于long
数据类型是64位有符号的Java原始数据类型,当计算到最高8位时,result
将是一个负数,我们构造的数据包Extended Payload Length64位全为1,查看其结果
可以看到,最终结果是为负数,接着我们查看代码继续调试,寻找当payload len为负数时,会发生什么
当处理完payload len之后,当前state=State.DATA
,由于已经计算出payload data的长度,此时需要用payload len来处理payload data,在processData
函数增加断点,并跟踪进去
接着进入processDataText
函数
进入transformation.getMoreData
函数
主要功能就是把payload data通过payload len一字节一字节的取出来放到dst
中,payloadWritten
从0自增。当取完payload len的数据之后,不会在意之后是否还存在数据,就会返回END_OF_FRAME
,如果payload len大于真实的payload data的长度,那么当发现inputBuffer
没有数据了,就会返回UNDERFLOW
,其他情况就会返回OVERFLOW
,所以如果payload len为负数,那么也会返回OVERFLOW
,接着返回到processDataText
函数中,我们认真观察这段代码
正常情况下,会因END_OF_FRAME
而直接不进入循环,当返回UNDERFLOW
或者OVERFLOW
两种状态时,会进入循环并进行messageBufferBinary
也就是payload data的utf-8解码,当我们将payload len变为负数传入时,解码结果将返回UNDERFLOW
,从而进入判断
由于payload len为负数,导致取payload data时返回的TransformationResult.OVERFLOW
,从而utf-8解码的这个循环结束,再次调用tr = transformation.getMoreData(opCode, fin, rsv, messageBufferBinary);
,进入读取payload data的循环,由于返回值依然是OVERFLOW
,这样就进入了无限循环,导致CPU爆满,那么为什么OVERFLOW
状态会导致这个循环的无限执行呢?
由于Websocket支持最大64位有符号数长度的payload data,而在取数据到messageBufferBinary
时,只有8192的容量,所以当数据过长一次没有全部读取到Buffer时,会返回OVERFLOW
用于后续循环再次读取
但是由于payload len为负时,错误地将其处理为OVERFLOW
状态,从而导致无限地循环,OVERFLOW
状态永远都不会变,这才是整个漏洞最主要的原因。
Exploit
参考github.com/RedTeamPentesting/CVE-2020-13935
效果
一个数据包即可
影响范围
- 9.0.0.M1~9.0.36
- 10.0.0-M1~10.0.0-M6
- 8.5.0~8.5.56
- 7.0.27~7.0.104
Tomcat Websocket协议BUG
分析
在调试过程中,发现当发送64位全1的Extended Payload Len时,最终计算的结果是-2,是-2,是-2
真实结果应该是-1才对啊,经过调试发现当进行32-39这8位数据时,发现result=-1
询问@fnmsd师傅才发现,是因为0xFF
是一个32位int
型数据,当32位数据最高位为1时,这就是个负数,应该进行long的强制类型转换,也就是说,想要把payload len变为负,不需要最高位置1,前32位置0,33位为1,也会成功触发攻击。
当跟踪其他对于byte
的数据转换时发现,基本都在可能出现问题的地方进行了强制类型转换
影响
对于协议来说,当发送的Websocket数据长度在2**31 — 2**32-1之间时,如果是漏洞版本Tomcat,将会直接触发漏洞,修补版本,会抛异常,当数据长度大于2**32-1时,计算出的payload len可能会小于真实长度,从而造成数据丢失。
参考
https://blog.redteam-pentesting.de/2020/websocket-vulnerability-tomcat/
https://cert.360.cn/warning/detail?id=c01c205c79fc1a9740f6ca9b133fb6f7