Files
SMT-ERP/smtERP-boot/docs/security-review-remediation-20261012.md
2026-10-13 16:15:00 +08:00

10 KiB
Raw Permalink Blame History

2026-10-12 代码审查修复及上线说明

本次以不改变正常业务计算、单据状态流转和原有数据为原则。认证变化已获调用方同步调整的确认。没有连接生产数据库、轮换真实凭据或发布服务。

审查项处理结果

审查项 本次处理 兼容性与剩余事项
C1 明文凭据 application.yml 的数据库用户名、数据库密码、Redis 密码改为外部注入;生成器配置移除默认用户名及密码 部署前配置环境变量;已泄露的真实口令仍须由部署人员轮换,Git 历史中的旧值没有清除
C2 LIKE 注入 4 个 Mapper、14 处表达式改为 concat('%', #{参数}, '%') 保留模糊搜索、中文、单引号和 % / _ 通配符语义,不对合法搜索词做截断或删字符
C3 匿名同步 两个 sync 接口仅接受独立 API Key 或已有 ERP 登录会话 外部调用方必须增加请求头,未配置密钥不允许匿名通过
C3 旧盘点 Login 返回独立短期 token;盘点业务接口要求认证;校验 USER_ID 和当前身份、盘点单所属盘点人;沿用登录失败限流 保留原大写字段和响应包装,增加 TOKEN 字段;禁止伪造他人身份与跨盘点人操作
C3 白名单 移除匿名 tenantClean;白名单前缀加路径边界;同步路径精确匹配 静态 Excel 模板仅精确 GET 公开,保留原 <a> / window.open 下载;模板不含业务数据
C4 eval WebSocket 使用 JSON.parse;菜单占位符使用显式映射 非 JSON 消息会被忽略;未知菜单表达式原样保留且不执行,见下文支持列表
H1 MD5/弱默认密码 完成入口核对及迁移设计,尚未改动存储与登录协议 现有库 password 为 varchar(50),不能直接存 BCrypt;详见迁移步骤。此风险尚未关闭
H2 排序/Example 保留已存在的 4 处字段及方向白名单,新增真实 MyBatis BoundSql 回归;分页排序加强为字段/方向语法校验 Example 条件由内部方法构造,未发现 Controller 接收外部 Example;不机械替换生成器 ${criterion.condition}
H3 公告 XSS 使用 DOMPurify 3.4.16 在展示时净化 HTML 保留普通 HTML 排版;历史记录不改写;后端存储端的富文本策略需覆盖其他消费者后再实施
H4 老依赖 JDBC 关闭 allowMultiQueries;新增 DOMPurify;修复 Java 11 测试工具兼容 Mapper 静态检查未发现分号批量语句;Spring Boot、axios、jQuery、fastjson 大版本升级仍需专项回归,未宣称已消除所有依赖漏洞
H5 会话/插件 Redis 会话有效期支持配置,默认沿用原 259200 秒 报告中的 servlet 36000 秒不是实际 ERP token 有效期;插件安装原本就校验管理员,不是普通登录用户可安装
M1 异常 HTTP 关闭失败记录日志、保留 POST 异常原因;日期格式试探仅捕获 ParseException;盘点内部错误记录日志而不向客户端暴露堆栈 日期格式依次尝试是合理流程,全部失败仍按原逻辑抛出业务错误;不改日期解析宽松程度
M2 API 权限 补齐本次同步与盘点入口及盘点身份/数据归属校验 全系统按钮权限到 API 权限的映射尚未实施,需要单独梳理角色、租户、共享数据和插件接口
M3 测试 新增认证、盘点权限、SQL、前端 XSS/eval 回归;移除 Maven 跳过测试编译配置;升级测试插件和测试 mock 工具以支持 Java 11 不使用真实数据库和 Redis,不等同于完整业务验收
L1/L2 printStackTrace 改结构化日志;保持局部 SimpleDateFormat 局部实例无共享线程安全问题;替换日期 API 可能改变宽松解析、时区等行为,本次不做无必要重构

部署前配置

使用服务管理器、容器配置或配置中心注入:

变量 用途 默认
DB_USERNAME 数据库账号 无,必填
DB_PASSWORD 数据库密码 无,必填
REDIS_PASSWORD Redis 密码 无,必填;无密码的 Redis 显式配置为空
SYNC_API_KEY 外部入库同步专用密钥 空;空值时仅 ERP 会话可以调用 sync
INVENTORY_TOKEN_TTL_SECONDS 旧盘点 token 绝对有效期 3600 秒;最低 60 秒
ERP_SESSION_TIMEOUT_SECONDS ERP Redis 会话滑动有效期 259200 秒,与原值一致;最低 60 秒

也可继续通过外部 Spring 配置覆盖 spring.datasource.* / spring.redis.*。不要把真实凭据重新写入仓库。MyBatis Generator 的 ${DB_USERNAME} / ${DB_PASSWORD} 是 Java 系统属性替换,运行生成器时需由本地构建配置提供这两个系统属性,不能假定它会自动读取同名环境变量。

生产数据库/Redis 凭据轮换应与服务配置更新一起安排,确认连接成功后撤销旧凭据;本次没有自行操作线上账号。同步请求和登录请求须经 HTTPS 或受保护的内部连接,不能在访问日志中记录认证头。

同步调用方改造

两个地址保持不变:

  • POST /apis/smtInbound/sync
  • POST /apis/headquartersInbound/sync

请求体和成功返回结构不变,增加 X-Sync-Api-Key: <SYNC_API_KEY>。密钥仅允许上述两个 POST 接口,不赋予其他接口权限。已有 X-Access-Token ERP 会话仍可调用。未提供认证、密钥错误或密钥未配置时返回 HTTP 401。

静态下载 /apis/smtInbound/template、/apis/headquartersInbound/template 保持匿名 GET;其他方法以及名称相似的路径不放行。tenantClean 现在必须登录,相关插件调用方需要携带原 ERP 会话头。

旧盘点客户端改造

  1. 原样 POST /apis/n2tpm/webmvc/Inventory/Login 或 /apis/webmvc/Inventory/Login,继续传 USER_NAME、PASSWORD。没有 context path 的部署同样支持这两个路径。
  2. 成功响应仍是 {code, msg, data};data.RESULT、data.USER_ID、data.DATA_AUTH 保留,新增 data.TOKEN。
  3. 后续所有查询、保存、接单、上传请求增加 X-Inventory-Token: <data.TOKEN>。HTTP 401 时重新登录,不能只凭 USER_ID 重试。
  4. USER_ID 必须是当前登录用户;盘点单必须分配给该用户;批量 DATA 中每条操作都校验。管理员如需代盘,应先通过原 ERP 分配流程调整盘点人,不能伪造 USER_ID。
  5. token 使用独立 Redis 命名空间并绝对过期,读取不会续期,不能当作 ERP 的 X-Access-Token 使用。也支持使用已有 ERP 会话调用盘点接口;同时提供两个头时优先验证盘点 token,避免身份混淆。
  6. 用户被停用/删除后,即使 token 未到期,业务服务也会拒绝操作。错误密码沿用现有 ERP 的账号限流策略。

菜单 URL 兼容范围

支持 {{ window._CONFIG.domianURL }}、其单/双引号方括号形式,以及 window.location.origin、window.location.host、window.location.protocol。普通菜单 URL 不变。其他自定义表达式不执行,需在核实数据库菜单配置后将确实需要的非敏感值增加到 menuUrl.js 的显式映射。

公告展示依赖 DOMPurify 官方发行版。当前使用 HTML profile,SVG/MathML/脚本等内容不保留。现有项目忽略 package-lock.json,本次遵循仓库规则,新增依赖在 package.json 中固定版本。

H1:口令迁移实施顺序(尚未执行)

  1. 先在预发布验证并扩展 password 至至少 varchar(100),增加算法标识或采用带算法前缀的存储格式;增加 must_change_password 标记。备份用户表,禁止批量覆盖旧口令。
  2. 当前网页在登录、注册、改密时发送 MD5,旧盘点发送明文后由服务端转 MD5,微信绑定也用 SQL 等值比较密码。必须把验证集中到同一密码服务,覆盖所有入口和密码写入路径,不能只改 insertUser。
  3. 过渡期可在服务端对旧协议的 MD5 凭据做 BCrypt,并在成功验证旧记录时有条件升级存储;明确这只改善数据库泄露风险,网络中的 MD5 仍是可重放凭据,必须配合 HTTPS。不得把 BCrypt 值写回仍按 MD5 等值比较的旧程序。
  4. 新协议传原始密码并由服务端 BCrypt/Argon2 校验;使用明确协议版本区分,不根据密码是否“看起来像 MD5”猜测。提供兼容期,统计旧客户端后再停用旧协议。
  5. 新建/重置用户使用单次临时随机密码或管理员指定强密码,返回一次并强制首次改密。先补管理界面的交付流程,避免用户创建成功却无法得知初始密码。旧弱口令账号的会话限制和强制改密需要前后端一起实现。
  6. 回归:旧账号、新账号、改密、重置、停用、限流、默认密码提醒、注册、微信绑定/登录、旧盘点,以及并发登录时的条件升级。升级后回滚代码必须继续能验证新哈希,不能直接回滚到只支持 MD5 的旧包。

H4/M2 后续专项范围(尚未执行)

  • 框架升级先验证插件框架 2.2.1、MyBatis 扩展、Redis、Swagger 与目标 Spring/JDK 的兼容矩阵,再选择受支持目标版本,不在这次安全补丁中跨代替换依赖。
  • axios/jQuery 升级需要覆盖拦截器、错误对象、上传下载、请求取消,以及打印、图片预览等依赖插件;fastjson 迁移需要验证实体绑定、日期/数值和插件序列化。
  • API 权限按现有菜单/按钮建立“HTTP 方法 + 路由 + 操作权限”映射,再覆盖管理员、普通用户、租户和插件接口的正反用例;不要直接把前端菜单可见性当成完整数据授权。

验证方式及边界

本地验证:后端 23 个测试通过;前端 3 组安全回归通过;生产构建成功退出。前端构建仍提示资源体积超限及 sass-loader 缺失,这些警告未在本次安全修复中处理。修改文件通过 UTF-8、BOM/替换字符和 git diff --check 检查。

后端运行 mvn -B test;前端运行 npm run test:security 和 npm run build。后端测试使用真实 MyBatis XML 生成 SQL,验证 14 个 LIKE 参数不进入 SQL 文本、正常排序继续生效、注入排序不生效;认证与盘点用例使用 mock,不连接外部服务。

上线前仍需在预发布环境完成:正常 ERP 登录、入库同步、新/旧盘点登录、本人盘点单接收和保存、图片上传、查询排序、模板下载、公告展示、插件管理员操作。自测通过不能保证所有业务完全无影响,尤其是外部客户端与数据库中的自定义菜单配置。