JeeSite Cloud 安全审计报告
0. 审计范围与限制
| 项 | 说明 |
|---|---|
| 目标 | JeeSite Cloud v5.18.1.springboot4,Spring Cloud 2025.1 + Spring Boot 4 微服务版 |
| 代码 | 仓库本体 modules/ gateway/ nacos/ sentinel/ config/ eureka/ zipkin/ 共 183 个 Java 文件(~2 万行)+ 39 个 yml/properties + 51 个 xml |
| 框架 | common/ git 子模块(JeeSite 5 框架)本次审计中已执行 git submodule update --init common 加载 |
| 限制 1 | Page、BaseController、FileUploadService、SqlMap 等核心类不在源码树内,来自编译后的 jeesite-common jar(Maven 依赖),相关框架层校验无法逐行确认,报告中标注「待确认」 |
| 限制 2 | sentinel/ 模块内嵌阿里 Sentinel Dashboard 1.8.8 第三方源码,nacos/ 内嵌 Nacos 3.2.2.1,按第三方组件对待 |
| 已确认 | 反序列化/RCE/命令执行:未发现一阶可利用点(无 ObjectInputStream/readObject/fastjson/Runtime.exec/表达式引擎;sentinel/pom.xml 中 fastjson 依赖已注释) |
1. 风险总览
| 编号 | 严重度 | 类型 | 位置 | 一句话描述 |
|---|---|---|---|---|
| H-1 | 🔴 高 | 未授权访问 + 硬编码密钥 + RCE 前置 | Nacos 配置 + NacosDataInitializer.java |
Nacos 认证关闭 + 内置已知默认 token 密钥 + 自动创建 nacos/nacos 管理员,可读取/篡改全部微服务配置,配合已知 Nacos 攻击链可达 RCE |
| H-2 | 🔴 高 | 硬编码凭证 + 弱口令集群 | 全部 yml/properties + docker-compose | MySQL root/123456、Redis 1234(prod 亦硬编码)、JeeSite system/admin、Nacos/Seata nacos/nacos、Sentinel sentinel/sentinel,且这些端口被 Docker 对外映射 |
| H-3 | 🟠 中高 | CORS | WebfluxCorsFilter.java |
网关反射任意 Origin 并暴露 x-token 头,放大 XSS/CSRF 利用 |
| M-1 | 🟠 中 | SSRF(潜伏,已禁用) | UeditorController.java / ImageHunter.java |
UEditor catchimage 抓图接口 SSRF:validHost 不拦 127.0.0.1/169.254.169.254 且跟随重定向;当前 action 已在框架中注释禁用,一旦启用即中高危 |
| M-2 | 🟠 中 | 未授权访问 + 越权(文件) | 网关路由 + Shiro 链 | /js/userfiles/** 匿名可读;文件上传/下载接口仅要求登录(/a/**=user),任意登录用户可上传/下载/删除任意文件(IDOR 面) |
| M-3 | 🟠 中 | SQL 注入(待确认) | TestDataDao.xml:24 + listData 链 |
${page.orderBy} 直接拼接,new Page<>(request,response) 绑定请求参数;框架 Page.setOrderBy 是否过滤在 jar 中无法确认 |
| M-4 | 🟠 中 | 未授权导出 / 越权 IDOR | TestData1Controller.java |
exportData 缺 @RequiresPermissions;各 Controller 按 id 直接 get/delete/updateStatus 无归属校验 |
| M-5 | 🟠 中 | 未授权访问(API 面) | 各 *-client Service/API |
Service 同时是 @RestController,权限注解在接口方法上,依赖 AOP 代理方式;/inner/api/** 仅靠内网 IP 白名单 |
| M-6 | 🟠 中 | 信息泄露 | application.yml:499 |
error.page.printErrorInfo: true 且 prod 未覆盖,500 页输出堆栈 |
| L-1 | 🟡 低 | 视图名注入 | DemoController.java |
路径变量拼进返回视图名(实际难利用) |
| L-2 | 🟡 低 | 存储型 XSS(面) | TestTree*Controller |
treeName 反射进 JSON,依赖前端渲染 |
| L-3 | 🟡 低 | CSRF / GET 改状态 | 各 delete/disable/enable | @RequestMapping 无 method 限制 |
| L-4 | 🟡 低 | 硬编码密钥(弱) | application.yml:458 |
shiro.loginSubmit.secretKey: "Base64" 固定字符串 |
| L-5 | 🟡 低 | 内网白名单过宽 | application.yml:462 |
innerFilterAllowRemoteAddrs: 127.0.0.1],10.,172.,192. 信任全部 RFC1918 |
| L-6 | 🟡 低 | 资源耗尽 | 文件配置 | 未配置上传大小上限 |
| L-7 | 🟡 低 | 第三方组件 | sentinel/、nacos/ |
内嵌第三方源码需跟踪版本漏洞 |
2. 高危详情
H-1 Nacos 认证关闭 + 已知默认 token 密钥 → 配置中心失守(可达 RCE)
位置
nacos/src/main/resources/application.properties:275→nacos.core.auth.enabled=false(认证主开关关闭)nacos/src/main/resources/application.properties:286-287→nacos.core.auth.server.identity.key/value=VkdocGMwbHpUWGxEZFhOMGIyMVRaV055WlhSTFpYa3dNVEl6TkRVMk56Zw==nacos/src/main/resources/application.properties:295→nacos.core.auth.plugin.nacos.token.secret.key= 同上nacos/src/main/resources/application.properties:267→nacos.security.ignore.urls含/v1/auth/**、/actuator/**nacos/src/main/java/com/jeesite/modules/initializer/NacosDataInitializer.java:53→ 启动时自动创建管理员nacos/nacos- 端口映射:
docker-compose-basic.yml→ 8848/9848/8849 全部对外
漏洞说明
- 认证主开关关闭:
nacos.core.auth.enabled=false意味着 Nacos 控制台(8849)与 OpenAPI(8848/9848)全部无需登录即可访问。任何能触达该端口的攻击者可直接:- 读取配置中心内全部微服务配置(含数据源密码、Redis 密码等敏感项);
- 修改/发布任意配置(各微服务
spring.config.import: nacos:*实时拉取 → 配置投毒); - 通过 Nacos 管理接口新建用户、绕过鉴权。
- 已知默认密钥:即使将来开启认证,
token.secret.key解码后实际为VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg→ 即众所周知的默认密钥ThisIsMyCustomSecretKey012345678(社区教程广泛使用的固定值)。攻击者可用该密钥离线伪造 Nacos 管理员 JWT,配合 Nacos 的 Derby SQL 注入 / 用户管理接口等已知利用链获取服务器 RCE。 nacos/使用 Nacos 3.2.2.1,含多模块内嵌 console(端口 8849)。
影响:配置中心完全失守 → 数据库/Redis 凭证泄露 → 集群内任意微服务配置篡改 → 结合 Nacos 已知利用链可达 RCE。这是本套件默认部署下的最高危项。
修复:部署前必须 ① 将 nacos.core.auth.enabled 设为 true;② 更换 token.secret.key 为强随机值(≥32 字节,单次 base64);③ 修改管理员密码;④ 限制 8848/8849/9848 仅内网可达;⑤ 收紧 nacos.security.ignore.urls 中 /actuator/**、/v1/auth/**。
H-2 集群默认弱口令 + 硬编码凭证(生产环境含明文密码)
位置汇总
| 组件 | 账号/密码 | 位置 | 端口映射 |
|---|---|---|---|
| MySQL(root) | root / 123456 |
docker-compose-basic.yml:41 |
23306→3306 对外 |
| MySQL(应用) | jeesite / jeesite |
modules/core/core/db/mysql/create_user.sql、application-prod.yml:10 |
— |
| MySQL(nacos) | nacos / nacos |
nacos/db/mysql/create_user.sql |
— |
| Redis | 1234(prod 亦硬编码,不可用环境变量覆盖) |
config/.../application.yml:370、application-prod.yml:41、docker-compose-basic.yml(valkey --requirepass 1234) |
26379→6379 对外 |
| JeeSite 登录 | system / admin |
README.md:291/330(默认初始化) | 8980 |
| Nacos 控制台 | nacos / nacos |
各模块 application.yml、NacosDataInitializer.java |
8849 对外 |
| Sentinel 控制台 | sentinel / sentinel |
sentinel/.../application.yml:112-113 |
9311 |
| Seata | nacos / nacos |
config/.../application.yml:293/346 |
7091 |
漏洞说明
- 所有默认密码均为公开已知弱口令,且 docker-compose 将 MySQL/Redis/Nacos 端口映射到宿主机。
application-prod.yml(配置中心推送的生产配置)中 Redis 密码为明文1234,未走环境变量,prod 与 dev 共用弱口令。- 各微服务 prod 配置中 Nacos 口令默认回退
nacos(${NACOS_PASSWORD:nacos})。
影响:任意一个被攻破即连锁——数据库被连 → 读取/篡改业务数据或 SELECT ... INTO OUTFILE/LOAD DATA 写文件;Redis 被连 → 写 WebShell/反序列化利用(取决于 Spring Data Redis 用法);Sentinel 控制台被连 → 可向业务客户端推送规则(限流/降级导致 DoS)。
修复:全部改为环境变量注入强密码并强制变更;application-prod.yml 删除明文 Redis 密码;关闭对外端口映射,仅内网/跳板机可达。
H-3 网关 CORS 反射任意 Origin(放大 XSS/CSRF)
位置 gateway/src/main/java/com/jeesite/modules/config/WebfluxCorsFilter.java:35-41
String origin = request.getHeaders().getFirst(HttpHeaders.ORIGIN);
if (origin != null) {
respHeaders.add("Access-Control-Allow-Origin", origin);
respHeaders.add("Access-Control-Allow-Headers", "content-type, x-requested-with, x-ajax, x-token, x-remember");
respHeaders.add("Access-Control-Expose-Headers", "x-token, x-remember");
}
说明:无条件反射请求 Origin,无白名单校验。未设置 Access-Control-Allow-Credentials,浏览器跨域请求默认不带 Cookie(缓解部分);但:
x-token(登录令牌头)被Access-Control-Expose-Headers暴露给任意跨域页面;- 任意恶意站点可直接跨域调用后端接口(自定义头通过预检),若应用某处存在 XSS 或令牌可获取,即可读走响应中的
x-token/数据; - 认证若未来切换为 Cookie 会话,本配置即变成经典凭证跨域窃取。
修复:改为明确 Origin 白名单(只允许已知前端域名),删除或收紧 Access-Control-Expose-Headers。
3. 中危详情
M-1 UEditor 远程抓图 SSRF(潜伏,当前已禁用)
位置
modules/core/files/src/main/java/com/jeesite/modules/file/web/rest/UeditorController.java:17-19→ 委托框架com.jeesite.common.ueditor.ActionEnter- 框架
common/.../ueditor/hunter/ImageHunter.java
url = new URL(urlStr);
if (!validHost(url.getHost())) { return ...; } // 仅校验域名
connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(true); // ← 跟随重定向,可绕过 host 校验
...
private boolean validHost(String hostname) {
InetAddress ip = InetAddress.getByName(hostname);
if (ip.isSiteLocalAddress()) { return false; } // ← 仅拦 RFC1918(10/172.16/192.168)
...
return !filters.contains(hostname);
}
漏洞点:
isSiteLocalAddress()不拦截127.0.0.1(回环)与169.254.169.254(云元数据/链路本地),0.0.0.0、IPv6-mapped::ffff:127.0.0.1、CGNAT100.64/10等同样不拦;setInstanceFollowRedirects(true)跟随 302,攻击者用一个公网地址 302 跳转到127.0.0.1/元数据地址即可完全绕过域名校验(目标不再重新校验);catcherLocalDomain默认仅["127.0.0.1","localhost","img.baidu.com"],只拦截字面量域名。
现状:框架 ActionEnter.java 中 ActionMap.CATCH_IMAGE 分支已注释,直接返回「该功能暂不提供支持」→ 当前不可利用。但代码完整保留,一旦有人启用(取消注释/覆盖),即可:探测内网任意端口、访问 169.254.169.254 云元数据获取实例 IAM 凭证(受 MIMEType 图片后缀限制,响应需为图片类型才回显,但仍可盲打+时序探测)。
修复:启用前必须重写 validHost(拦回环/链路本地/元数据/所有私有地址,禁跟随重定向或重定向后再校验),或永久移除该功能。
M-2 文件服务未授权访问 + 越权(IDOR)
位置
- 路由:
config/.../jeesite-cloud-gateway.yml:58→/js/a/file/**、/js/userfiles/**、/js/static/filePreview/**→ 文件服务 - Shiro 链:
config/.../application.yml:467-470/inner/api/** = inner /api/** = user ${adminPath}/** = user (adminPath=/a)/userfiles/**不在链上 → 匿名可访问 - 框架
common/.../modules/file/web/FileUploadController.java:upload、download/{fileUploadId}、fileList、params
漏洞点:
/js/userfiles/**直接匿名读取上传根目录文件。若上传白名单允许.html/.svg,任意登录用户可上传恶意 HTML/SVG,匿名访问即构成存储型 XSS / 恶意文件托管 / 钓鱼载体;上传接口返回完整 URL,便于构造。- 文件上传/下载/删除/列表接口仅 Shiro
user(登录)即可访问,无业务权限校验、无文件归属校验:任意登录用户可按fileUploadId下载/操作他人上传的文件(需结合 jar 内FileUploadService确认是否校验 bizKey/创建人,仓库内无源码)。 - 上传校验(扩展名白名单、魔数校验、文件名随机化)在
jeesite-commonjar 中,无法源码确认;FileUploadController.download/{fileUploadId}按 ID 而非路径取文件,路径穿越风险相对低,但 ID 是否可枚举/可预测待确认。
修复:① 收紧上传扩展名白名单(禁 html/svg/jsp 等);② 下载/删除接口增加归属与业务权限校验;③ /userfiles 若必须匿名(前端展示),对敏感文件加鉴权或随机不可枚举文件名。
M-3 orderBy SQL 注入(经典 JeeSite 注入点,待框架确认)
位置
modules/test1/test1/src/main/resources/mappings/modules/test/TestDataDao.xml:24(test2/test3 同型模板):ORDER BY ${page.orderBy}- 注入链:
TestData1Controller.java:70(test2/3 同型)→testData.setPage(new Page<>(request, response))→Page.orderBy绑定请求参数orderBy→CrudService.findPage()→ 框架生成 SQL 拼接 TestDataDao.xml:7-12:SELECT ${sqlMap.column.toSql()} FROM ${sqlMap.table.toSql()} WHERE ${sqlMap.where.toSql()} ORDER BY ${sqlMap.order.toSql()}(sqlMap 由框架从实体安全构建,风险集中在page.orderBy)
漏洞点:
${page.orderBy}是确凿的 MyBatis${}字符串拼接 sink;page.orderBy来自 HTTP 请求参数orderBy,可被用户完全控制;- 关键未知:框架
jeesite-commonjar 中Page.setOrderBy是否做白名单/字符过滤 —— 源码不在本仓库,无法确认。JeeSite 5.x 官方曾修复过 orderBy 注入(历史 CVE),但此 jar 版本未在源码可见范围; TestDataDao.findListForMap当前无调用方(潜伏),但一旦接线即直接可利用。
触发方式(若框架未过滤):GET /a/test1/testData/listData?orderBy=(select 1 from (select sleep(5))a) 等布尔/时间盲注,与登录用户同库权限执行任意 SQL。
修复:对 orderBy 做列名白名单(仅允许实体已知列 + asc/desc);Mapper 中禁止 ${page.orderBy} 直拼,改用白名单映射。
M-4 未授权导出 + 数据操作越权(IDOR)
位置
modules/test1/test1/src/main/java/com/jeesite/modules/test/web/TestData1Controller.java:97-104:exportData唯一缺@RequiresPermissions的方法,无权限限制导出全部业务数据(过滤条件为空时全表),test2/test3同型。- 各 Controller(TestData1/2/3、TestTree1/2/3)的
get/@ModelAttribute(id)、delete、disable/enable、updateStatus、save均直接按请求参数id加载/操作记录,无记录归属/数据范围校验。
触发方式:
- 低权限已登录用户
GET /a/test1/testData/exportData批量导出全表数据(越权读取); - 持
test:testData:edit权限用户遍历/猜测 id →POST /a/test1/testData/delete?id=<任意ID>删除/停用/启用任意记录; TransTestController.test(/a/trans/test)无任何权限注解,任意登录用户可反复触发向test_data/test_tree写数据并人为制造异常(数据污染/轻微 DoS)。
修复:exportData 补充 @RequiresPermissions;所有按主键操作增加数据范围/归属校验;trans 测试接口加权限并限制调用频率。
M-5 微服务 API 暴露面(权限注解落位待确认)
位置
modules/test1/test1-client/.../api/TestDataServiceApi.java、test2/.../TestTreeServiceApi.java、test3/.../TestDataServiceApi.java- 各
Service同时是@RestController(modules/test1/.../service/TestDataService.java:28-30等),暴露/api/test1/testData/**、/api/test2/testTree/**、/inner/api/test3/testData/** @RequiresPermissions声明在接口方法上,实现类方法上无注解
漏洞点:
- 若 Shiro 注解 AOP 基于 CGLIB 代理(Spring 默认对无接口类或
proxy-target-class=true),接口上的@RequiresPermissions可能不被识别,API 仅依赖/api/**=user、/inner/api/**=inner的登录/内网过滤而无方法级授权 → 任一登录用户可调增删改查; /inner/api/test3/**设计为内网调用,但inner过滤器仅校验来源 IP 是否在127.0.0.1,10.,172.,192.白名单(见 L-5),内网横向可达即等同未授权。
修复:确认代理方式;在实现类方法上显式加注解;/inner/api/** 收紧为服务间共享密钥/JWT 鉴权,而非仅 IP。
M-6 生产环境错误详情泄露
位置 config/src/main/resources/jeesite-cloud-yml/application.yml:497-500
error:
page:
printErrorInfo: true
说明:application-prod.yml 未覆盖该值,生产环境 500 页向客户端输出异常堆栈(内部类名、SQL、路径)。修复:prod 设为 false。
4. 低危详情
| 编号 | 位置 | 问题 |
|---|---|---|
| L-1 | DemoController.java:44-58 |
dataGrid/{viewName}、form/{viewName} 将路径变量 StringUtils.cap 后拼进返回视图名。{viewName} 仅匹配单段路径,.. 无法跨段,实际难利用;属「用户输入直入视图解析」坏味道,建议白名单枚举 |
| L-2 | TestTree1Controller.java:209、TestTree2Controller.java:208 |
treeName/treeCode 为用户存储值,反射进 JSON 树节点;若前端 innerHTML 渲染即存储型 XSS(需前端确认) |
| L-3 | 各 delete/disable/enable/updateStatus |
@RequestMapping 无 HTTP method 限制,GET 即可触发状态变更;若框架无 GET CSRF 校验,可 <img> 诱导触发 |
| L-4 | application.yml:458 |
shiro.loginSubmit.secretKey: "Base64" 为固定字符串占位,登录表单加密密钥公开已知(影响登录提交加密强度) |
| L-5 | application.yml:462 |
innerFilterAllowRemoteAddrs: 127.0.0.1],10.,172.,192. 信任全部 RFC1918 内网 → 同网段任意主机可当"内网"访问 /inner/api/** |
| L-6 | 各 application.yml |
未配置 spring.servlet.multipart.max-file-size/max-request-size,登录用户可上传超大文件耗尽磁盘/带宽 |
| L-7 | sentinel/、nacos/ |
内嵌第三方组件(Sentinel Dashboard 1.8.8、Nacos 3.2.2.1)及 management.endpoints.web.exposure.include: '*'(sentinel)、Nacos ignore urls 含 /actuator/**,需持续跟踪上游 CVE 并限制端口暴露 |
5. 修复建议(按优先级)
- 【P0】Nacos 加固:开启认证、更换随机 token 密钥与管理员密码、限制端口仅内网、收紧 ignore urls。
- 【P0】凭证全部更换:MySQL/Redis/Nacos/Seata/Sentinel/JeeSite 默认口令改为强随机并经环境变量注入;prod 配置删除明文 Redis 密码。
- 【P0】网关 CORS 白名单:替换反射 Origin 为白名单。
- 【P1】orderBy 白名单:在框架 jar 版本确认
Page.setOrderBy过滤逻辑前,对所有暴露orderBy参数的列表接口做列名白名单校验。 - 【P1】文件服务:上传扩展名白名单收紧、文件接口加归属/业务权限、评估
/userfiles匿名面。 - 【P1】权限补齐:
exportData/trans等缺注解端点补@RequiresPermissions;确认 API 接口注解在实现类的落位;收紧/inner/api鉴权。 - 【P2】纵深:prod 关
printErrorInfo;/actuator收敛;上传大小限制;Sentinel/Nacos 版本跟踪;UEditor 抓图功能保持禁用或重写 SSRF 校验。
6. 附录:审计动作与审计面
- 工具/方式:
grep危险模式全库扫描(反序列化/RCE/命令执行、${}SQL 拼接、SSRF URL 构造、硬编码凭证、JWT/密钥)、逐个读取关键 Controller/Service/Mapper/配置文件;两路子代理分别深读 test1/2/3 与 core/files 模块后交叉核对;git submodule update --init common加载框架源码核实框架层问题。 - 已排查未发现问题:反序列化/命令执行/RCE 一阶无利用点;fastjson 依赖已注释;无从请求 URL 发起外部请求的一阶代码(SSRF 集中于框架 UEditor,且已禁用)。
- 审计期间变更:初始化了
common/子模块(执行git submodule update --init common,README 中标准步骤),工作区出现子模块检出内容,如需还原请git submodule deinit common。 - 覆盖模块:modules(core/files/test1/2/3 及 client)、gateway、nacos、config、sentinel、eureka、zipkin、parent、docker-compose、bin。