0%

Hutool JSONUtil拒绝服务漏洞(CVE-2022-45690 CVE-2022-45689)

Hutool JSONUtil拒绝服务漏洞(CVE-2022-45690 CVE-2022-45689)

产品介绍

Hutool是一个功能丰富且易用的Java工具库,通过诸多实用工具类的使用,旨在帮助开发者快速、便捷地完成各类开发任务。 这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作, 可以满足各种不同的开发需求。

漏洞概述

CVE-2022-45690: 该项目受影响版本存在拒绝服务漏洞,由于org.json.JSONTokener.nextValue::JSONTokener.java组件中并未检验json的嵌套深度。攻击者可以通过构造特制的JSON或XML数据在程序解析时导致JVM溢出从而实现拒绝服务(DoS)攻击。

CVE-2022-45689:该项目受影响版本存在拒绝服务漏洞,由于JSONObject.java中JSONObject方法对于要解析的json并未检验是否合法,在后续解析JSON时就会因为内存不足而造成拒绝服务漏洞。

受影响版本

< Hutool 5.8.11

漏洞分析

调试环境

使用idea新建基于maven的java项目,在pom.xml中引入hutool

image-20230811111844774

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hutool-all-version>5.7.22</hutool-all-version>
</properties>

<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all-version}</version>
</dependency>
</dependencies>

编写测试类

image-20230811112000265

1
2
3
4
5
6
7
8
9
10
11
package org.example;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

public class Main {
public static void main(String[] args) {
String json = "json_str";
JSONObject jsonObject = JSONUtil.parseObj(json);
}
}

漏洞原理

一般我们复现漏洞时都需要利用一些泄露的有关漏洞的信息,比如别人发的通告中未打码完全的截图、漏洞描述等信息,这里我们从阿里云漏洞库的描述中了解到该漏洞的大致情况:

image-20230811112316260

里面有两个关键信息,一是嵌套深度、二是栈溢出,那这不妥妥的无线递归导致的栈溢出漏洞吗?先整2000层嵌套打一下试试:

image-20230811133440365

果然栈溢出了,打完收工,回家吹空调了。

啥?你说这太简单了,你想学点硬货?行,那咱上硬货。

这个漏洞分析第一步肯定时先把断点打起来,我们发现所谓的parseObj其实就是new了一个JSONObject对象。

image-20230811133657015

创建对象时会调用init方法,在该方法中会根据我们输入的参数类型选择不同的重载函数进行解析,我们输入的时个字符串,所以会进入到第680行的逻辑里。

image-20230811133906215

这里干的什么注释说的已经很清楚了

image-20230811134053659

这里有两个函数解释以下,首先时nextClean会去获取json字符串的token也就是" { } : [ ]这一些,nextValue会去获取json字符串键对应的值。这个漏洞就出现在757行将json的键值对,往JSONObject对象里放的时候,在获取json值得时候会调用netValue函数。

image-20230811134202285

这个函数会根据从:后解析到得第一个token类型去调用不同得逻辑,当解析的token为{时就会再new一个JSONObject对象。啊,这这这,这不就递归了吗?

image-20230811134541231

我们知道递归过程中函数会不断地压栈,而栈空间是有限地,如果不断地压栈而不弹栈地话就会导致栈溢出,所以需要一个基例来帮助函数解析到最深处能够回退,而在json字符串解析过程中就是当解析到最后一个{时开始回退。但是这个json字符串地长度再这里我们若是可以控制地话,就可以先搞几万层嵌套让它一直压栈直到溢出为止,因为再分析过程中并没有看到代码中有对嵌套深度地限制。

这个漏洞是栈溢出漏洞,而(CVE-2022-45689)确实一个实打实得堆溢出漏洞,一般来说堆溢出时非常少见得,应为JVM堆是非常大的,我们可以使用下面的代码答应一下一个普通的java程序运行时JVM可用的堆内存情况。

image-20230811152225458

从结果来看JVM初始化时使用的总的堆内存为241MB,而可用的最到堆内存达到3GB,一般来说时很难发生溢出的。我们知道,JVM堆内存中一般存储的时java对象,要想要JVM堆发生溢出也就是说要有大量的没有被GC回收的对象占满了内存,但是如果只是单纯得常见对象,这些对象在沾满了幸存者0区后会被GC清除掉,一般也不会导致堆溢出,除非这些对象时不可清除得,那么什么情况下得对象时不可清除得呢。GC在进行垃圾回收时可以采用多种策略,其中包括引用计数法,即查看一个对象是否有被别得对象引用,如果没有则可以清除,如果有则不清除,但是这个算法有缺陷解决不了循环引用得问题,所以又有了可达性分析算法,及从一系列得GC Roots开始分析一个对象得可达性,如果这个对象不可达则进行清除。在这个例子中进行JSONObject解析递归到最深层进行返回时,每一个{xx:1}都将被new为一个新的JSONObject对象并被不断地引用嵌套,最终所有的JSONObject对象一起链接在一起占满了堆内存,我们可以使用jconsole来观察一下JVM的堆内存变化情况:

image-20230811153435062

jconsole可能不太直观,可以下载virtual VM进行监控

下载地址:https://github.com/oracle/visualvm/releases/download/2.1.6/visualvm_216.zip

image-20230811154033340

最开始我们的程序休眠了20秒(为了给jconsole链接提供时间)休眠结束后开始进行json字符串的解析,开始阶段应为还没有递归到最深处所以堆内存增加量不大,在递归达到最深处函数不断返回时,堆内存空间的消耗不断走高,最终耗尽内存资源导致堆溢出。我们把高峰期的堆内存dump出来

image-20230811154905943

可以看到足足1.7G的内存都被我们的json字符串占据。

修复措施

  1. 升级软件到不受影响的版本或最新版,官方仓库地址:https://github.com/dromara/hutool/releases。

参考链接

Buy me a coffee.

欢迎关注我的其它发布渠道