JSH_ERP安全审计报告

32次阅读
没有评论

管伊佳ERP(jshERP)安全审计报告

审计时间: 2026-08-11
审计对象: jshERP-boot(SpringBoot 2.0.0 / Java 8 / MyBatis-Plus 3.0.7.1)+ jshERP-web(Vue 2.7 / Ant Design Vue)
审计范围: 反序列化、命令执行/RCE、SQL 注入、未授权访问、硬编码凭据、XSS、SSRF、越权/IDOR 等可获取系统/用户/应用权限与数据的问题
审计方式: 源码级只读审计 + 5 个并行深度子代理 + 关键链路手工核验。未修改任何文件。


1. 总体结论

该 ERP 系统存在 1 条可直接打穿的完整 RCE 链、多处严重越权/提权、大量未授权接口与敏感信息泄露,以及若干高危放大器。综合评级:高危。

核心根因:整个后端只有 LogCostFilter 这一个认证过滤器,且它只校验"是否登录"、从不校验"角色/权限/归属";而系统又默认开放自助注册、默认 admin/123456、默认关闭验证码。三条路叠加,任意匿名访问者可轻松获得高权限乃至服务器代码执行。

最严重的攻击链(端到端)

匿名访问者
  │  ① 默认开放注册 /user/registerUser (后端不校验 register_flag)
  ▼
任意普通用户
  │  ② /user/update  传入 {"id":1,"password":"<攻击者md5>"}  →  越权重置超管密码
  │   (updateByPrimaryKeySelective, 仅校验"已登录", 不校验归属)
  ▼
超管 admin 登录
  │  ③ /plugin/uploadInstallPluginJar  上传构造的 pf4j 插件 jar
  │   (starblues springboot-plugin-framework 2.2.1, runMode=prod, 无类型/签名校验)
  ▼
服务器任意代码执行 (RCE)

即使不考虑注册开放,默认凭据 admin / 123456 + 验证码默认关闭(checkcode_flag 在初始化 SQL 中未插入)也可直接完成 ①。


2. 漏洞汇总表(按严重程度排序)

# 严重程度 类型 位置 一句话结论
1 🔴 严重 任意代码执行 RCE controller/PluginController.java:243-263 admin 上传 pf4j 插件 jar → uploadPluginAndStart 动态加载执行,任意代码运行在 JVM
2 🔴 严重 越权/垂直提权 service/UserService.java:176-192 任意已登录用户可 updateByPrimaryKeySelective 重置**任意用户(含超管)**密码
3 🔴 严重 认证体系失效(根因) filter/LogCostFilter.java:47 全站仅校验"是否登录",无任何角色/权限/归属校验;且默认开放注册、默认 admin/123456
4 🟠 高 越权/水平提权 service/UserBusinessService.java(updateUserBusiness) 可自行把角色绑定为管理员,一步提权
5 🟠 高 未授权访问 config/Swagger2Config.java + LogCostFilter.java:16 /doc.html、/v2/api-docs、/webjars 匿名暴露全部接口文档
6 🟠 高 越权/IDOR 全库 getXxx(id)/update/delete 接口 按 id 直操作,无租户/归属过滤,跨租户任意读改删
7 🟠 高 任意文件删除 service/SystemConfigService.java:350-381 单据 fileName 字段可含 ../,删除单据时 Files.delete 任意文件
8 🟠 高 任意路径安装插件/写配置 controller/PluginController.java:217-235, 270-288 admin 可 installByPath 加载服务器任意 jar、uploadConfigFile 任意文件写入
9 🟠 高 敏感信息泄露 controller/UserController.java(/user/info?id=) 返回任意用户对象,含 MD5 口令散列
10 🟡 中 未授权文件读取 controller/SystemConfigController.java:228-286 + LogCostFilter.java:17 /systemConfig/static/** 免登录读取上传根目录任意文件(跨租户)
11 🟡 中 存储型 XSS service/MsgService.java + jshERP-web/.../ShowAnnouncement.vue:19 公告 msgContent 原样存储,前端 v-html 渲染
12 🟡 中 前端代码注入(eval) jshERP-web/src/utils/util.js:113 菜单 URL 模板用 eval() 渲染 {{...}} 占位符
13 🟡 中 SSRF(配置驱动/盲) service/UserService.java:958-988、utils/HttpClient.java weixinUrl 等平台配置指向内网时,/user/weixinLogin 免登录触发内网/云元数据请求
14 🟡 中 硬编码凭据 application.yml:17-24 DB root/123456、Redis 1234abcd;默认账号 admin/jsh/test123 均为 MD5("123456")
15 🟡 中 弱口令存储 utils/Tools.java:582-588 + Login.vue:206 无盐 MD5,且口令在前端 MD5 后传输
16 🟡 中 租户隔离失效 config/TenantConfig.java + utils/Tools.java:619 tenant_id 由 token 后缀解析,无后缀 → 0 → 超级管理员无任何租户过滤
17 🟡 中 SQL 注入放大器 application.yml:17 allowMultiQueries=true 允许堆叠查询(当前无已利用注入点,加固项)
18 🟢 低 fastjson 反序列化 sink utils/ExtJsonUtils.java:99 fastjson 1.2.83 默认 autoType 关闭暂不可利用;响应序列化解析用户可控 ext 字段,属理论风险
19 🟢 低 开放重定向 jshERP-web/src/permission.js:52-53 登录后按 from.query.redirect 跳转,可跳任意站
20 🟢 低 过期依赖 pom.xml:116-120 commons-io 1.3.2(2011 年版本),无直接可利用调用点

3. 根因分析:认证与授权体系

3.1 只校验登录,不校验权限

jshERP-boot/src/main/java/com/jsh/erp/filter/LogCostFilter.java

@WebFilter(filterName = "LogCostFilter", urlPatterns = {"/*"}, ...)   // 覆盖所有路径
public class LogCostFilter implements Filter {
    public void doFilter(...) {
        Object userId = redisService.getObjectFromSessionByKey(servletRequest,"userId");  // 仅查"是否登录"
        if(userId!=null) { //如果已登录,不阻止
            chain.doFilter(request, response);
            return;
        }
        // ... 白名单放行匿名请求
        servletResponse.setStatus(500);
        servletResponse.getWriter().write("loginOut");   // 未登录则拒绝
    }
}
  • 只校验 X-Access-Token → Redis 中是否有 userId,不校验角色、不校验资源归属。
  • 因此:任何已登录用户(哪怕刚注册的普通租户用户)可调用全部接口,包括管理接口、跨租户数据接口。
  • 唯一特权判断散布在各 Controller 里,形式为 BusinessConstants.DEFAULT_MANAGER.equals(userInfo.getLoginName())(即 loginName=="admin" 字符串比对),且多数写接口根本没有这个判断。

3.2 认证体系默认关闭了三道闸门

闸门 现状 证据
注册开关 后端 registerUser 不校验 register_flag,前端才做判断 jshERP-web/src/views/user/Login.vue:349-350;service/UserService.java:612
登录验证码 仅当平台配置 checkcode_flag=="1" 才启用,初始化 SQL 未插入该配置 → 默认关闭 service/UserService.java validateCaptcha;docs/jsh_erp.sql
默认口令 超管 admin / 普通账号 jsh、test123 口令均为 e10adc... = MD5("123456") docs/jsh_erp.sql:997-999;constants/BusinessConstants.java:160,208

3.3 匿名白名单过大

LogCostFilter.java:16-18,52-55 放行的匿名路径包括:

/jshERP-boot/platformConfig/getPlatform
/jshERP-boot/v2/api-docs
/jshERP-boot/webjars
/jshERP-boot/systemConfig/static
/jshERP-boot/api/plugin/wechat/weChat/share
/jshERP-boot/api/plugin/general-ledger/pdf/voucher
/jshERP-boot/api/plugin/tenant-statistics/tenantClean
/jshERP-boot/user/login | register | weixinLogin | weixinBind | registerUser | randomImage
/jshERP-boot/doc.html

其中 registerUser、weixinLogin、systemConfig/static、swagger 文档均造成下文的具体漏洞。


4. 严重漏洞详情

4.1 [严重] 插件 JAR 上传 → 动态加载 → RCE

位置: controller/PluginController.java:243-263
依赖: springboot-plugin-framework 2.2.1(starblues,pom.xml:27-30)+ application.yml:47 plugin.runMode: prod

@PostMapping("/uploadInstallPluginJar")
public BaseResponseInfo install(MultipartFile file, HttpServletRequest request, HttpServletResponse response){
    User userInfo = userService.getCurrentUser();
    if(BusinessConstants.DEFAULT_MANAGER.equals(userInfo.getLoginName())) {   // 仅"admin"字符串比对
        pluginOperator.uploadPluginAndStart(file);   // 上传并立即加载/启动插件
        res.code = 200; res.data = "导入成功";
    }
}

利用链:

  1. 获得 admin 登录态(默认 admin/123456、验证码默认关闭,或经 §4.2 越权重置超管密码)。
  2. POST /jshERP-boot/plugin/uploadInstallPluginJar multipart 上传一个构造的 pf4j 插件 jar。
  3. starblues 在 runMode=prod 下执行 uploadPluginAndStart:把 jar 安装进插件目录并立即创建插件 Spring 上下文、注册其 Controller、调用插件 Bootstrap.start()。
  4. 插件 jar 中任意代码在 JVM 内执行;JAR 内容与文件名均无类型/白名单/签名校验。

危害: 服务器任意代码执行/命令执行(等价 RCE)。若进程以 root 运行则完全控制主机;插件同时暴露 /api/plugin/** 路由,可持续驻留。

修复建议:

  • 移除或默认关闭插件动态加载能力(runMode: dev,或去掉上传安装接口);
  • 插件必须做签名校验 / 受信来源限制 / 二次认证,不依赖单一 loginName=="admin" 字符串判断;
  • 对 uploadInstallPluginJar、installByPath、uploadPluginConfigFile 增加独立的、强认证的管理通道。

4.2 [严重] 越权重置任意用户密码 → 直达 RCE

位置: service/UserService.java:176-192(updateUser)

public int updateUser(JSONObject obj, HttpServletRequest request) throws Exception{
    User user = JSONObject.parseObject(obj.toJSONString(), User.class);   // 含 id/password,全由客户端提供
    ...
    Object userId = redisService.getObjectFromSessionByKey(request,"userId");
    if (userId != null) {                       // 只校验"已登录",不校验操作对象是否属于自己
        result = userMapper.updateByPrimaryKeySelective(user);   // 按客户端 id 更新任意用户
    }

配套问题:

  • resetPwd(UserService.java:217)只禁止 "admin".equals(loginName),但没有拦 updateUser 这条更宽的路径;
  • controller/UserController.java 的 /user/update、/user/resetPwd 均接受 id + password(MD5);
  • updateByPrimaryKeySelective 无租户/归属过滤,跨租户可改;
  • loginName 也可被改写,普通用户可把自己的 loginName 改成 admin。

利用链: 任一登录用户 → PUT /jshERP-boot/user/update 发送 {"id":1,"password":"<攻击者md5>"} → 超管密码被重置 → 登录 admin → §4.1 插件 RCE。

修复建议: 写接口一律校验"只能操作自己 / 租户内且角色允许"的对象;id、password、loginName 等敏感字段不允许由请求体直接映射;updateUser 与 resetPwd 统一防护。


4.3 [高] 自行绑定管理员角色(水平提权)

位置: service/UserBusinessService.java(updateUserBusiness)
UserBusiness 表保存 userId → 角色/机构/仓库/权限 绑定。updateUserBusiness 无任何权限校验,updateByPrimaryKeySelective 按请求体更新。普通用户可把 keyId(角色 id)改为管理员角色(如 roleId=10 或角色 id=1),自行提权为管理员,绕过 §4.2。

修复建议: 角色绑定只能由超管操作;对 keyId、type(role/user/org 等)做服务端归属校验。


4.4 [高] 任意 IDOR(越权访问/修改/删除数据)

位置: 全库 Controller + Service。几乎所有 getXxx(id)、updateXxx、deleteXxx、batchDeleteXxx 接口都直接以请求中的 id/ids 查询或写库,没有任何"数据属于当前用户/租户"的过滤。

  • 典型: /depotHead/getDetail?id=、/user/info?id=、/material/list、各 batchDeleteXxxByIds。
  • 配合 §3.1,低权限/跨租户账号可读取、修改、删除任意租户的单据、商品、客户、用户等数据。

修复建议: 建立统一的数据归属校验(当前登录用户/租户 vs 数据 owner),在 Service 层强制过滤;ids 批量操作同样校验。


4.5 [高] 未授权访问:Swagger 全量接口文档

位置: config/Swagger2Config.java + LogCostFilter.java:16

@EnableSwagger2
Docket docket = new Docket(DocumentationType.SWAGGER_2)
    .select()
    .apis(RequestHandlerSelectors.any())   // 所有接口
    .paths(PathSelectors.any())            // 所有路径
  • /jshERP-boot/v2/api-docs、/jshERP-boot/doc.html、/jshERP-boot/webjars/** 均在匿名白名单中。
  • 攻击者可离线拿到全部接口的参数、实体结构、示例请求,大幅降低其余漏洞(越权、注入、插件上传等)的利用成本。

修复建议: 生产环境关闭 swagger(profile 隔离);至少把 swagger 路径从匿名白名单移除并要求登录+管理员。


4.6 [高] 任意文件删除(路径穿越)

位置: service/SystemConfigService.java:350-381(deleteFileByPathList)

for (String path : pathArr) {
    ...
    Path sourcePath = Paths.get(filePath + File.separator + path);   // path 未做任何校验
    Path targetPath = Paths.get(filePath + File.separator + DELETED + File.separator + path);
    ...
    Files.copy(sourcePath, targetPath, ...);
    Files.delete(sourcePath);                                        // 删除任意文件
}

利用链: 单据/商品记录的 fileName 字段由请求体直接映射(如 DepotHeadService.addDepotHeadAndDetail 的 JSONObject.parseObject(beanJson, DepotHead.class),无过滤)。认证用户创建单据时把 fileName 设为 ../../../../var/www/.../config.yml 之类的路径,再调用删除单据接口(batchDeleteDepotHeadByIds → deleteFileByPathList),Files.delete 即删除目标文件。getDirByPath 本身允许 /,故斜杠不受限、.. 不被清理。

危害: 任意文件删除 → 删除配置、数据文件、.jar、应用自身文件,造成 DoS/业务中断。

修复建议: 删除前对路径做规范化(toRealPath)并强制校验位于上传根目录内;fileName 入参时即过滤 ..。


4.7 [高] 敏感信息泄露:任意用户口令散列

位置: controller/UserController.java(/user/info?id=)

User user = userService.getUser(id);    // 返回完整 User,含 password 字段(MD5)
  • /user/info?id= 对任意 id 返回用户对象,password 字段未被置空。
  • 拿到超管口令散列后可离线破解(MD5 无盐,见 §4.14)或直接复用。

修复建议: 查询出参统一 setPassword(null)(或 DTO 过滤);同样检查其他返回 User 的接口。


5. 中危漏洞详情

5.1 未授权文件读取(跨租户)

位置: controller/SystemConfigController.java:228-286(/systemConfig/static/**)+ LogCostFilter.java:17 白名单

@GetMapping(value = "/static/**")
public void view(...) {
    String imgPath = extractPathFromPattern(request);
    imgPath = imgPath.replace("..", "");                     // 弱过滤
    fileUrl = systemConfigService.getFileUrlLocal(imgPath);  // filePath + "/" + imgPath
    inputStream = new BufferedInputStream(new FileInputStream(fileUrl));
  • 免登录即可读取上传根目录 /opt/jshERP/upload 下任意文件(其他租户上传的附件、导入的 Excel、导出的单据等)。
  • LogCostFilter.java:41 拦截 ..、%2e + replace("..","") 双重抑制,限制读取范围在上传根目录内,无法逃逸到 /etc/passwd(信息泄露面可控但真实存在)。
  • 若 fileUploadType=2(OSS),则按 aliOss_linkUrl 拼接 URL 发起请求,存在配置驱动 SSRF(§5.4)。

修复建议: 从匿名白名单移除,改为登录+租户校验;或按租户子目录隔离;用白名单扩展名+规范化路径。


5.2 存储型 XSS

位置: 后端 service/MsgService.java(insertMsg)+ 前端 jshERP-web/src/components/tools/ShowAnnouncement.vue:19

<!-- 前端:公告展示 -->
<span v-html="record.msgContent"></span>
  • MsgService.insertMsg 仅排除 loginName=="admin",对任意普通用户开放,msgContent 原样入库(只判断"不是 admin 才允许发公告")。
  • 前端用 v-html 直接渲染 → 普通用户可发公告,<img src=x onerror=...> / <script> 在所有看公告的用户浏览器里执行,可窃取管理员会话、构造钓鱼界面。

修复建议: 后端对 msgContent 做 HTML 编码/白名单富文本清洗;前端改为文本渲染或使用 DOMPurify 过滤。


5.3 前端菜单 URL eval 代码注入

位置: jshERP-web/src/utils/util.js:113

let URL = (item.url||'').replace(/{{([^}}]+)?}}/g, (s1, s2) => eval(s2));
  • 菜单 url 字段来自后端菜单表(数据库),模板占位符 {{...}} 被 eval() 直接执行。
  • 结合 §4.4/§5.2,任何能改菜单数据或诱导管理员访问含恶意占位符菜单的路径,都能在登录用户浏览器执行任意 JS。
  • 即使目前无直接入口,eval 本身也是明确的高危反模式,应移除。

修复建议: 用静态映射/replace 字典替代 eval;菜单 url 在前端白名单化。


5.4 SSRF(配置驱动,未授权触发)

位置: service/UserService.java:958-988(getUserByWeixinCode)、service/PlatformConfigService.java:199-221、utils/HttpClient.java:32-55

String weixinLogin = platformConfigService.getPlatformConfigByKey("weixinUrl").getPlatformValue() + WEIXIN_LOGIN;
String url = weixinLogin + "?appid=" + weixinAppid + "&secret=" + weixinSecret + "&js_code=" + weixinCode + "&grant_type=authorization_code";
JSONObject jsonObject = HttpClient.httpGet(url);   // 服务端发起请求
  • weixinUrl/aliOss_linkUrl 等存于 jsh_platform_config 表,写入需 admin,但触发免登录:/user/weixinLogin、/user/weixinBind 在白名单。
  • 一旦平台配置被(恶意)admin 指向内网地址,任意未授权请求即可让服务器对 http://127.0.0.1:*、http://169.254.169.254/*(云元数据)或内网任意端口发起 GET;响应仅提取 openid/access_token 返回,故主要为盲 SSRF。

修复建议: 服务端出站请求统一经 SSRF 防护(禁内网/链路本地地址、限协议与端口);weixinUrl 校验 https 域名;敏感平台配置不返回给低权限用户。


5.5 硬编码凭据 / 默认口令

位置: application.yml:17-24、docs/jsh_erp.sql:997-999、constants/BusinessConstants.java:160,208、service/UserService.java:157-161

# application.yml
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/jsh_erp?...&allowMultiQueries=true&...&useSSL=false
    username: root
    password: 123456
  redis:
    password: 1234abcd
-- docs/jsh_erp.sql
-- 内置账号: admin / jsh / test123
-- password = 'e10adc3949ba59abbe56e057f20f883e'  = MD5('123456')
// service/UserService.java:157-161 —— 新增用户默认口令
String password = "123456";
password = Tools.md5Encryp(password);
  • 数据库 root/123456、Redis 1234abcd 硬编码在配置仓库中;useSSL=false。
  • 所有内置账号口令均为 MD5("123456")。

修复建议: 凭据改由环境变量/密钥管理(如 Vault/KMS)注入;强制修改默认口令;开启验证码与登录限流。


5.6 弱口令存储:无盐 MD5 + 前端 MD5 传输

位置: utils/Tools.java:582-588(md5Encryp)、jshERP-web/src/views/user/Login.vue:206

// 无盐 MD5
MessageDigest md = MessageDigest.getInstance("MD5");
return new BigInteger(1, md.digest()).toString(16);
// Login.vue:206 —— 口令在前端先 MD5 再发送
loginParams.password = md5(values.password);
  • 无盐 MD5 可用彩虹表/GPU 快速破解;所有账号共享同一哈希空间,泄露一个库全部可离线爆破。
  • 前端 MD5 不能替代 HTTPS/加盐哈希,反而把"前端已知的哈希"变成凭证,重放即可登录(配合 /user/info?id= 拿到的散列,无需明文即可登录任意账号)。

修复建议: 服务端采用带盐慢哈希(BCrypt/Argon2);口令不落库明文/前端散列;全站强制 HTTPS。


5.7 租户隔离失效

位置: config/TenantConfig.java + utils/Tools.java:619(getTenantIdByToken)

// token 格式: UUID_tenantId
// 无 "_" → 返回 0 → 超级管理员 → TenantSqlParser 返回 null → 不注入 tenant_id 过滤
  • 多租户过滤完全依赖"从客户端 token 后缀解析 tenantId";解析不到则按超级管理员处理,不加任何过滤。
  • jsh_platform_config/jsh_tenant/jsh_sequence/jsh_function/jsh_sys_dict_* 表被 doTableFilter 排除在租户过滤之外,任何租户用户都可读全局配置/字典/功能菜单。
  • 叠加 §4.4,跨租户数据访问基本畅通。

修复建议: tenantId 由服务端从会话/密钥派生而非 token 后缀;超管行为单独白名单化;平台配置类表按角色再过滤。


5.8 SQL 注入:当前未发现可利用注入点,存在放大器

结论:对全库 61 个 mapper XML 的 ${} 穷举 + Java 侧排查,未发现可利用的经典 SQL 注入。该 fork 已对历史 CVE 做了系统性加固:

${} 类型 数量 来源 判定
${criterion.condition} 232 处 MyBatis Generator 生成,算子硬编码(addCriterion("id =", ...) 为 protected),值走 #{} 安全
${orderByClause} 29 处 service 层 17 处 setOrderByClause 全部为字符串常量("sort asc, id desc" 等),无请求参数 安全
${column} / ${order} 各 4 处 前端可控,但 Controller 先过 StringUtil.safeSqlParse(),XML 内 OGNL 白名单(仅 5 个固定列名、asc/desc) 安全

Java 侧:无 @Select 注解内嵌 SQL、无字符串拼接 SQL、无 MyBatis-Plus 原生 Wrapper 用户可控调用、无原生 JDBC;ids 批量经 StringUtil.strToLongList() 转 Long;分页排序经 SqlUtil.escapeOrderBySql()(白名单正则 [a-zA-Z0-9_\ ,\.]+,长度≤500)过滤。

真实风险(加固项): application.yml:17 的 allowMultiQueries=true 允许一条语句内堆叠执行多条 SQL。当前虽无已利用注入点,但一旦未来出现任何注入(回归、二次注入、插件/租户模块),即可从"只读 SELECT"升级为"堆叠 INSERT/UPDATE/DELETE/DDL 接管数据库"。

修复建议: allowMultiQueries=false;保留 #{} + 白名单体系;对 safeSqlParse 覆盖所有 Ex 查询入口做回归测试。


6. 低危 / 理论风险

6.1 fastjson 1.2.83 反序列化 sink(理论)

utils/ExtJsonUtils.java:99 在每次 API 响应序列化时对实体 ext 字段执行 JSON.parseObject((String) value)(单参,数据可来自用户输入)。全库未发现 setAutoTypeSupport(true)/addAccept/setSafeMode(false),1.2.83 默认 autoType 关闭,当前 @type 载荷会抛 AutoTypeNotSupportException。若未来升级 fastjson 或开启 autoType,即成 RCE sink。 建议后续升级 2.x 或切换 Jackson。

6.2 开放重定向

jshERP-web/src/permission.js:52-53 登录后 next({ path: decodeURIComponent(from.query.redirect) }) 按 URL 参数跳转,可被引导到任意站(钓鱼)。建议限制为站内路径白名单。

6.3 过期依赖

  • commons-io 1.3.2(2011 年,pom.xml:116-120):存在已知 CVE,本库无用户输入直接驱动的调用点。
  • spring-boot-starter-redis 1.4.1、swagger-bootstrap-ui 1.6、SpringBoot 2.0.0(2018 年):整体框架版本过老,建议升级 LTS。

6.4 排查后确认非漏洞项(避免误报)

  • utils/ComputerInfo.java:50 Runtime.getRuntime().exec(command):command 为硬编码数组(ipconfig/ifconfig),不受用户输入控制。
  • 通用文件上传 SystemConfigService.uploadLocal/uploadAliOss:已做 bizPath 防穿越 + 扩展名白名单(无 jsp 等可执行类型)+ 文件名重命名(时间戳),不可写 webshell。
  • Excel 导入(jxl 内存解析,不落盘):仅校验 .xls 扩展名,无写入面。
  • /systemConfig/static 路径穿越:被过滤器 + replace("..","") 双重抑制,读取范围被限制在上传根目录内,非任意文件读取。

7. 修复优先级建议

P0(立即,封堵 RCE 与提权链)

  1. 修复 §4.2 updateUser/resetPwd 越权:写接口强制归属校验,敏感字段(id/password/loginName/roleId)不允许请求体直接映射。
  2. 修复 §4.3 角色绑定提权:updateUserBusiness 仅超管可用。
  3. 收紧 §4.1 插件上传:默认关闭插件动态加载,接口加签名/二次认证/受信来源。
  4. 关闭 §5.8 allowMultiQueries,消除堆叠注入放大器。
  5. 修改默认口令并强制开启验证码/限流;撤销初始化 SQL 中弱口令。

P1(尽快,消除未授权与数据泄露)

  1. 把 swagger、/systemConfig/static/**、registerUser 从匿名白名单移除;后端校验 register_flag。
  2. 修复 §4.7 /user/info?id= 返回口令散列;出参统一置空 password。
  3. 修复 §4.6 任意文件删除:路径规范化 + 上传根目录强制校验。
  4. 修复 §5.2 存储型 XSS、§5.3 eval。

P2(规划加固)

  1. 口令哈希改 BCrypt/Argon2,全站 HTTPS,前端去除 MD5。
  2. 服务端出站请求加 SSRF 防护;敏感平台配置不外泄。
  3. 租户隔离改服务端派生 tenantId,去除"无后缀即超管"逻辑。
  4. 升级依赖(SpringBoot LTS、fastjson→Jackson、commons-io)。

8. 附录:关键文件索引

关注点 文件
认证过滤器/匿名白名单 jshERP-boot/src/main/java/com/jsh/erp/filter/LogCostFilter.java
租户解析 .../config/TenantConfig.java、.../utils/Tools.java:619
用户越权写 .../service/UserService.java(updateUser/resetPwd/registerUser)
角色绑定提权 .../service/UserBusinessService.java(updateUserBusiness)
插件 RCE .../controller/PluginController.java
文件删除穿越 .../service/SystemConfigService.java:350-381
未授权文件读取 .../controller/SystemConfigController.java:228-286
SSRF 出站请求 .../utils/HttpClient.java、.../service/UserService.java:958-988
fastjson sink .../utils/ExtJsonUtils.java:99
无盐 MD5 .../utils/Tools.java:582-588
swagger .../config/Swagger2Config.java
XSS 前端 sink jshERP-web/src/components/tools/ShowAnnouncement.vue:19
eval 注入 jshERP-web/src/utils/util.js:113
开放重定向 jshERP-web/src/permission.js:52-53
硬编码凭据 jshERP-boot/src/main/resources/application.yml、docs/jsh_erp.sql

本报告由只读代码审计生成,审计过程未修改、未执行、未部署任何代码。所有利用链均为理论分析,未在实际目标上验证。

正文完
 0
评论(没有评论)