GeoServer jsonArrayContains 未授权RCE:一个jsonb\_path\_exists引入的SQL注入0day
零日手记
零日手记
随笔漫记安全路
2026年8月16日 09:35
北京
在小说阅读器读本章
去阅读
8月13日,安全研究员秋风在X上公开了一个GeoServer的0day——通过jsonArrayContains函数未授权SQL注入,数据库高权限下直接RCE。CVSS 9.8,国内暴露资产5320个。官方已合并修复PR但尚未发布修复版本。
GeoServer是什么
GeoServer是OSGeo维护的开源地理空间数据服务器,实现了WMS、WFS、WCS等OGC标准服务。说白了就是个地图服务器,政府、企业、科研机构用得很多。底层依赖GeoTools做JDBC数据访问和CQL/ECQL过滤器到SQL的转换。漏洞就出在这个转换环节。
CVSS 3.1评分9.8。PoC、EXP、技术细节均已公开。暂无CVE编号。
漏洞原理
GeoTools的GEOT-7589给PostgreSQL 12+引入了jsonb\_path\_exists优化。引入前,jsonArrayContains走旧版jsonb @>编码逻辑,对应值会做转义。引入后,第三参数expected直接字符串拼接进SQL,没有转义、没有参数化。
代码层面的sink长这样:
Object value = ((LiteralExpressionImpl) expected).getValue();
return \"(@.%s == \\\"%s\\\")\".formatted(jsonPath[lastIndex], value);
这个value没有经过JSONPath转义,没有经过SQL literal encoder,也没有进入JDBC bind参数,被同时嵌套在JSONPath双引号、SQL单引号和jsonb\_path\_exists()调用中。
正常输入生成:
jsonb_path_exists(\"json_column\"::jsonb, '$ ? (@.key == \"expected\")')
攻击者控制expected,就能依次退出JSONPath字符串、SQL字符串和外层SQL表达式,改变后续SQL语法结构。标准SQL注入。
攻击链路
匿名GetFeature + CQL_FILTER
→ ECQL parser
→ JsonArrayContainsFunction(column, path, expected)
→ JDBCFeatureSource.getCountInternal()
→ FilterToSqlHelper.encodeJsonArrayContains()
→ constructEquality() [原始拼接sink]
→ Statement.executeQuery(countSql)
→ PostgreSQL
通过WFS GetFeature的CQL\_FILTER参数注入恶意SQL,先能做数据泄露和篡改。数据库连接用户是超级用户或持有pg\_execute\_server\_program权限时,通过PostgreSQL的COPY...TO PROGRAM执行系统命令:
COPY (SELECT 1) TO PROGRAM '<command>'
命令以数据库OS用户身份在/bin/sh中执行,可以反弹shell、写webshell、横向移动。
注入需要穿越三层结构:JSONPath双引号 → SQL单引号 → jsonb\_path\_exists外层表达式。闭合后用分号开始新语句,用SQL注释吞掉尾部。注意WFS和WMS查询形状不同,payload不能互换。
利用条件
不是随便一个GeoServer就能RCE,需要同时满足:
- WFS服务未要求认证
- 后端使用PostgreSQL/PostGIS数据存储
- 存在能被当作JSON解析的属性
- PostgreSQL >= 12(12以下不走新分支)
- 实现RCE需数据库角色是superuser或有pg\_execute\_server\_program权限
没有PostgreSQL/PostGIS数据存储时根本不进入这个sink。普通权限角色只能SQL注入读写数据,不能执行命令。
容易被忽略的一点:GeoTools的preparedStatements=true和pgJDBC的preferQueryMode=extended都不能作为缓解措施。pgJDBC 42.7.11默认就是extended,但驱动仍会拆分复合查询依次执行。preparedStatements复用同一个FilterToSqlHelper,raw concat sink依然存在。
影响范围
受影响版本:
- GeoServer 2.24.5 ~ 3.0.0(GeoTools 30.5 ~ 35.0),由GEOT-7589(2024年6月引入)回归
- 已确认:GeoServer 2.26.0(GeoTools 32.0)
- GeoTools 30.4/31.2及以下不含新分支,不受影响
资产暴露情况:
- 国内5320个,关联IP 353个
- 全球34038个,关联IP 1515个
PoC状态
GitHub已有完整研究仓库(mhtsec/GeoServer-jsonArrayContains-PG-RCE),含exploit.py、隔离复现环境、原理文档。还有社区补丁(ubitquity/UBITQUITY-GeoServer-Security-Patch)通过escapeLiteral()做转义缓解。
PoC使用:
# 基线检查
python3 exploit.py --url http://127.0.0.1:8085/geoserver --auto --check
# 离线预览(不发请求)
python3 exploit.py --url http://127.0.0.1:8085/geoserver \\
--workspace jac_sqli --layer poi_info --column mp \\
--cmd \"id > /tmp/marker.txt\"
# 实际执行(仅限授权环境)
python3 exploit.py --url http://127.0.0.1:8085/geoserver \\
--auto --cmd \"id > /tmp/marker.txt\" --execute
利用是盲的——HTTP响应不返回命令输出。Multiple ResultSets只代表堆叠语句信号,不能证明命令执行成功,必须在数据库端核对marker文件和statement log。
修复建议
官方已合并修复PR(GeoTools #5829,GEOT-7958),对expected参数调用escapeJsonLiteral()转义。但截至发稿修复版本尚未发布,临时缓解:
- 限制或关闭WFS对外暴露,或强制认证
- 数据库连接用最小权限用户,撤销superuser和pg\_execute\_server\_program
- WAF拦截CQL\_FILTER中的jsonArrayContains、COPY、PROGRAM、pg\_sleep、pg\_read\_file等关键字
- 数据库置独立非特权环境,不挂载宿主敏感目录
根治必须消除expected参数的原始拼接,用jsonb\_path\_exists变量对象承载比较值,通过JDBC bind传入。黑名单和WAF关键字过滤不够可靠。
参考资料
[1] 原始披露 - https://x.com/q1uf3ng/status/2087490992723407096[1]
[2] PoC仓库 - https://github.com/mhtsec/GeoServer-jsonArrayContains-PG-RCE[2]
[3] 社区补丁 - https://github.com/ubitquity/UBITQUITY-GeoServer-Security-Patch[3]
[4] GeoTools修复PR #5829(GEOT-7958)- https://github.com/geotools/geotools/pull/5829[4]
[5] GeoTools GEOT-7589(漏洞引入)- https://github.com/geotools/geotools/pull/4805[5]
引用链接
[1]https://x.com/q1uf3ng/status/2087490992723407096
[2]https://github.com/mhtsec/GeoServer-jsonArrayContains-PG-RCE
[3]https://github.com/ubitquity/UBITQUITY-GeoServer-Security-Patch
[4]https://github.com/geotools/geotools/pull/5829
[5]https://github.com/geotools/geotools/pull/4805
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论