jeesite-cloud-安全审计报告

51次阅读
没有评论

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 全部对外

漏洞说明

  1. 认证主开关关闭:nacos.core.auth.enabled=false 意味着 Nacos 控制台(8849)与 OpenAPI(8848/9848)全部无需登录即可访问。任何能触达该端口的攻击者可直接:
    • 读取配置中心内全部微服务配置(含数据源密码、Redis 密码等敏感项);
    • 修改/发布任意配置(各微服务 spring.config.import: nacos:* 实时拉取 → 配置投毒);
    • 通过 Nacos 管理接口新建用户、绕过鉴权。
  2. 已知默认密钥:即使将来开启认证,token.secret.key 解码后实际为 VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg → 即众所周知的默认密钥 ThisIsMyCustomSecretKey012345678(社区教程广泛使用的固定值)。攻击者可用该密钥离线伪造 Nacos 管理员 JWT,配合 Nacos 的 Derby SQL 注入 / 用户管理接口等已知利用链获取服务器 RCE。
  3. 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);
}

漏洞点:

  1. isSiteLocalAddress() 不拦截 127.0.0.1(回环)与 169.254.169.254(云元数据/链路本地),0.0.0.0、IPv6-mapped ::ffff:127.0.0.1、CGNAT 100.64/10 等同样不拦;
  2. setInstanceFollowRedirects(true) 跟随 302,攻击者用一个公网地址 302 跳转到 127.0.0.1/元数据地址即可完全绕过域名校验(目标不再重新校验);
  3. 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

漏洞点:

  1. /js/userfiles/** 直接匿名读取上传根目录文件。若上传白名单允许 .html/.svg,任意登录用户可上传恶意 HTML/SVG,匿名访问即构成存储型 XSS / 恶意文件托管 / 钓鱼载体;上传接口返回完整 URL,便于构造。
  2. 文件上传/下载/删除/列表接口仅 Shiro user(登录)即可访问,无业务权限校验、无文件归属校验:任意登录用户可按 fileUploadId 下载/操作他人上传的文件(需结合 jar 内 FileUploadService 确认是否校验 bizKey/创建人,仓库内无源码)。
  3. 上传校验(扩展名白名单、魔数校验、文件名随机化)在 jeesite-common jar 中,无法源码确认;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)

漏洞点:

  1. ${page.orderBy} 是确凿的 MyBatis ${} 字符串拼接 sink;
  2. page.orderBy 来自 HTTP 请求参数 orderBy,可被用户完全控制;
  3. 关键未知:框架 jeesite-common jar 中 Page.setOrderBy 是否做白名单/字符过滤 —— 源码不在本仓库,无法确认。JeeSite 5.x 官方曾修复过 orderBy 注入(历史 CVE),但此 jar 版本未在源码可见范围;
  4. 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. 修复建议(按优先级)

  1. 【P0】Nacos 加固:开启认证、更换随机 token 密钥与管理员密码、限制端口仅内网、收紧 ignore urls。
  2. 【P0】凭证全部更换:MySQL/Redis/Nacos/Seata/Sentinel/JeeSite 默认口令改为强随机并经环境变量注入;prod 配置删除明文 Redis 密码。
  3. 【P0】网关 CORS 白名单:替换反射 Origin 为白名单。
  4. 【P1】orderBy 白名单:在框架 jar 版本确认 Page.setOrderBy 过滤逻辑前,对所有暴露 orderBy 参数的列表接口做列名白名单校验。
  5. 【P1】文件服务:上传扩展名白名单收紧、文件接口加归属/业务权限、评估 /userfiles 匿名面。
  6. 【P1】权限补齐:exportData/trans 等缺注解端点补 @RequiresPermissions;确认 API 接口注解在实现类的落位;收紧 /inner/api 鉴权。
  7. 【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。
正文完
 0
评论(没有评论)