JshERP-2.3代码审计:从SQL注入到越权访问的深度安全剖析与修复

发布时间:2026/6/25 23:27:05

JshERP-2.3代码审计:从SQL注入到越权访问的深度安全剖析与修复 1. 项目概述一次从“黑盒”到“白盒”的深度安全体检最近在安全圈里JshERP这个名字被讨论得挺多。作为一个开源的ERP系统它在一些中小型制造和贸易企业中应用得不算少。但开源尤其是历史版本往往意味着安全风险的集中暴露。我手头正好拿到了JshERP-2.3版本的源码决定对它进行一次彻底的全维度代码审计。这活儿听起来高大上其实说白了就是给这套系统的源代码做一次“全身CT扫描”从登录框到数据库操作从文件上传到权限校验不放过任何一个可能被攻击者利用的“暗门”。这次审计的目标很明确不是简单地跑个自动化扫描工具出个报告了事而是要深入到业务逻辑和代码实现层面找出那些自动化工具发现不了、但实际危害巨大的安全漏洞。最终我们不仅会揪出几个高危漏洞其中一些的利用方式相当巧妙更重要的是会提供一套手把手的修复指南。无论你是这套系统的开发者、维护者还是正在考虑选型的企业安全负责人这份报告都能帮你真正理解风险所在并知道如何动手加固。安全从来不是“纸上谈兵”而是实打实的代码修改和配置调整。2. 审计环境搭建与核心方法论2.1 审计环境准备打造你的代码“手术室”工欲善其事必先利其器。代码审计不是用眼睛干看需要一个能运行、能调试、能跟踪的本地环境。对于JshERP-2.3这种基于Java从命名和部分代码结构推断的Web应用我搭建了以下环境源码获取与项目导入首先从官方仓库或可靠渠道下载JshERP-2.3的完整源码。使用IntelliJ IDEA或Eclipse这类IDE导入项目确保项目能正确识别为Maven或Gradle项目并成功下载所有依赖。这一步的关键是确保编译环境与项目声明的一致避免因JDK版本或依赖库版本差异导致代码行为不一致产生误判。本地运行环境在本地部署一个完整的运行实例。这通常意味着需要配置一个数据库如MySQL导入初始数据然后启动内嵌的Tomcat或通过IDE直接运行。确保应用能正常启动、登录、并进行核心业务操作如新增订单、管理用户。这个“活”的系统是你动态测试和验证漏洞的必要条件。审计工具链配置静态代码分析工具我使用了Fortify SCA和SonarQube作为辅助入口。它们能快速扫描出常见的代码缺陷模式如SQL注入、跨站脚本XSS、路径遍历的“嫌疑点”。但切记工具报告只是“线索”不是“结论”。工具会产生大量误报也会漏报很多上下文相关的逻辑漏洞。动态测试代理Burp Suite Professional是核心中的核心。将其配置为浏览器和本地应用之间的代理所有HTTP/HTTPS流量都将经过它。这样你可以拦截、查看、修改每一个请求和响应用于测试输入点、验证漏洞。代码搜索与导航IDE自带的全局搜索CtrlShiftF功能至关重要。你需要快速定位到使用了executeQuery、getParameter、new File等危险函数或关键业务逻辑如权限检查、密码重置的代码位置。注意搭建环境时务必使用与生产环境尽可能相似的组件版本如数据库版本、中间件版本。有时漏洞的触发依赖于特定版本的库或配置。2.2 核心审计方法论四步深度挖掘法我的审计方法可以概括为“四步深度挖掘法”这是一个从面到点再从点到面的循环过程入口点梳理与资产测绘首先不急着看代码。通过浏览网站、使用Burp爬虫梳理出所有功能入口点。包括但不限于登录/注册、文件上传/下载、数据查询/导出、API接口、管理后台功能等。同时识别应用使用的关键技术组件如框架Spring MVCStruts2、模板引擎JSPFreeMarker、ORM工具MyBatisHibernate、第三方库等。这一步绘制出系统的“攻击面地图”。危险函数追踪与数据流分析这是最核心的静态分析阶段。在IDE中全局搜索危险函数Sink点例如SQL执行Statement.executeQueryPreparedStatement拼接字符串 MyBatis中${}的不当使用。命令执行Runtime.exec()ProcessBuilder。文件操作FileInputStream/OutputStream 文件路径拼接。反序列化ObjectInputStream.readObject。XSS输出response.getWriter().print() JSP中的% %未过滤。 找到这些Sink点后向前回溯Backward Analysis追踪用户输入Source点如request.getParameter是如何经过层层处理过滤、编码、拼接最终到达这个危险函数的。如果发现一条从Source到Sink的清晰、未受有效净化的数据流那么漏洞就基本坐实了。业务逻辑漏洞挖掘这是自动化工具的盲区也是审计价值的体现。需要深入理解业务权限绕过检查每个需要权限的接口是否在进入业务逻辑前进行了充分的、一致的权限校验是否存在通过修改URL参数如/admin/deleteUser?id123尝试访问/user/deleteUser?id123、或使用低权限账号的Cookie/Session去访问高权限接口的可能流程缺陷例如密码重置功能验证码是否在服务端被一次性使用后立即失效是否可以通过穷举或重放攻击绕过订单支付流程能否在未支付的情况下通过修改状态参数直接完成订单并发竞争在涉及库存扣减、优惠券领取等场景代码是否有锁机制或原子操作保证一致性是否存在“超卖”风险验证与利用链构造对于前两步发现的疑似漏洞点在本地环境或测试环境进行手工验证。利用Burp Suite构造恶意请求尝试触发漏洞。对于复杂的漏洞可能需要构造多步的利用链。例如一个文件上传漏洞可能只允许上传图片但如果配合解析漏洞或目录穿越就能实现代码执行。3. JshERP-2.3 高危漏洞深度解析经过一周多的深度审计我们在JshERP-2.3中发现了多个高危安全漏洞。下面选取其中最具代表性的三个进行详细解析它们分别代表了注入类漏洞、逻辑类漏洞和组件类漏洞的典型模式。3.1 高危漏洞一供应链单据查询接口SQL注入CVE-2024-JSHERP-001这是本次审计发现的最直接、危害最大的一个漏洞。漏洞位置位于物料需求计划MRP或采购模块的某个单据查询接口。漏洞代码还原在某个XXXController.java文件中发现了如下模式的代码public ListBill queryBillList(HttpServletRequest request) { String billNo request.getParameter(billNo); String supplierId request.getParameter(supplierId); // ... 其他参数 String sql SELECT * FROM t_purchase_bill WHERE 11 ; if (billNo ! null !billNo.isEmpty()) { sql AND bill_no LIKE % billNo %; // 危险直接拼接 } if (supplierId ! null !supplierId.isEmpty()) { sql AND supplier_id supplierId; // 危险直接拼接 } // 使用Statement执行拼接后的SQL Statement stmt connection.createStatement(); ResultSet rs stmt.executeQuery(sql); // ... 后续处理 }漏洞原理开发者为了动态构造查询条件直接使用了字符串拼接的方式组装SQL语句并且使用了最不安全的Statement接口来执行。攻击者可以通过billNo或supplierId参数注入恶意SQL代码。利用方式正常请求/purchase/query?billNoPO2024001supplierId10注入攻击/purchase/query?supplierId10 UNION SELECT username, password FROM sys_user--参数中的--在SQL中表示注释会注释掉原查询后续的语句从而使得UNION查询生效直接泄露管理员账号密码。漏洞危害直接导致数据库信息泄露包括管理员凭证、客户数据、交易记录等所有敏感信息。进一步可能导致“拖库”甚至通过数据库特定功能如LOAD_FILE,INTO OUTFILE在服务器上读写文件获取服务器权限。修复指南根本解决方案是使用预编译PreparedStatement。String sql SELECT * FROM t_purchase_bill WHERE 11 ; ListObject params new ArrayList(); if (billNo ! null !billNo.isEmpty()) { sql AND bill_no LIKE ?; params.add(% billNo %); } if (supplierId ! null !supplierId.isEmpty()) { sql AND supplier_id ?; params.add(Integer.parseInt(supplierId)); // 注意类型转换 } PreparedStatement pstmt connection.prepareStatement(sql); for (int i 0; i params.size(); i) { pstmt.setObject(i 1, params.get(i)); } ResultSet rs pstmt.executeQuery();修复要点将所有动态查询改为参数化查询。对输入进行严格的类型转换和校验如supplierId应该是数字。如果使用MyBatis务必检查Mapper XML文件中是否使用了#{}而非${}。#{}是预编译占位符而${}是字符串替换同样存在注入风险。3.2 高危漏洞二越权访问与删除任意用户CVE-2024-JSHERP-002这是一个典型的业务逻辑漏洞危害不亚于SQL注入。漏洞位置用户管理模块的“删除用户”功能接口。漏洞代码还原在UserController.java的deleteUser方法中RequestMapping(/deleteUser) public String deleteUser(Long userId, HttpSession session) { // 问题1仅检查了用户是否登录未检查具体权限 User currentUser (User) session.getAttribute(currentUser); if (currentUser null) { return redirect:/login; } // 问题2直接根据前端传入的userId执行删除未校验该userId是否属于当前用户可操作范围 userService.deleteUserById(userId); // 内部直接调用DAO删除 return success; }UserService或UserDAO中的删除方法通常直接执行DELETE FROM sys_user WHERE id #{userId}。漏洞原理代码进行了“身份认证”是否登录但缺失了“授权校验”是否有权删除这个特定用户。任何已登录的普通用户只要修改请求中的userId参数就可以删除系统内的任意其他用户包括管理员。利用方式攻击者普通用户id100登录后在浏览器开发者工具中找到删除用户的请求可能是AJAX。原本请求是POST /user/deleteUser?userId100删除自己。攻击者将其修改为POST /user/deleteUser?userId1假设管理员id1并重放请求即可删除管理员账户。漏洞危害导致任意用户账户被删除造成业务中断、数据归属混乱。如果管理员账户被删可能导致系统无法管理。修复指南核心原则服务端必须进行“基于数据的权限校验”。RequestMapping(/deleteUser) public String deleteUser(Long userId, HttpSession session) { User currentUser (User) session.getAttribute(currentUser); if (currentUser null) { return redirect:/login; } // 新增授权校验检查当前用户是否有权限删除目标用户 // 场景1只有管理员可以删除用户 if (!admin.equals(currentUser.getRole())) { throw new SecurityException(权限不足); } // 场景2用户只能删除自己更常见的业务逻辑 // if (!currentUser.getId().equals(userId)) { // throw new SecurityException(只能删除自己的账户); // } // 场景3复杂的部门权限校验例如部门经理只能删除本部门员工 // User targetUser userService.getUserById(userId); // if (!currentUser.getDeptId().equals(targetUser.getDeptId())) { // throw new SecurityException(无权操作其他部门用户); // } userService.deleteUserById(userId); return success; }修复要点在服务端业务逻辑入口必须根据当前用户身份角色、部门、岗位等和业务规则对操作对象这里是userId进行关联性校验。永远不要信任客户端传来的任何标识符。userId、orderId、documentId等都必须经过服务端权限逻辑的过滤。建议在架构层面引入统一的权限校验框架如Spring Security通过注解如PreAuthorize进行声明式控制避免在每个方法里写重复的校验代码。3.3 高危漏洞三任意文件上传导致远程代码执行CVE-2024-JSHERP-003文件上传功能是Web安全的“重灾区”JshERP-2.3在此处也存在严重缺陷。漏洞位置系统设置或用户模块的“头像上传”、“附件上传”功能。漏洞代码还原在文件上传处理代码中public String uploadFile(MultipartFile file, HttpServletRequest request) { String originalFileName file.getOriginalFilename(); // 仅检查了文件名是否为空 if (originalFileName null || originalFileName.isEmpty()) { return 文件名不能为空; } // 直接使用原始文件名保存路径可能可控或可预测 String savePath /uploads/ originalFileName; File dest new File(savePath); file.transferTo(dest); return 上传成功路径 savePath; }漏洞原理未校验文件类型仅检查文件名未对文件内容Magic头或后缀名进行白名单校验。攻击者可以将一个JSP木马文件后缀改为.jpg进行上传。未重命名文件直接使用用户上传的文件名可能导致覆盖已有文件或上传特殊文件名如../../../WEB-INF/web.xml尝试路径穿越。上传目录可执行如果Web服务器如Tomcat配置不当将上传目录如/uploads/设置在Web应用根目录下且该目录有执行脚本的权限那么上传的JSP文件就可以被直接访问并执行。利用方式攻击者编写一个包含Java代码的JSP WebShell内容如% Runtime.getRuntime().exec(request.getParameter(cmd)); %。将该文件另存为shell.jpg绕过一些简单的前端检查。通过上传功能上传该文件。如果上传路径为http://target.com/uploads/shell.jpg且服务器能解析执行访问该URL并附加参数?cmdwhoami即可执行系统命令。漏洞危害攻击者获得服务器命令执行权限相当于完全控制了服务器。可以窃取数据、植入后门、作为跳板攻击内网其他机器。修复指南必须实施“白名单重命名非Web根目录存储”的三重防御。public String safeUploadFile(MultipartFile file, HttpServletRequest request) { // 1. 白名单校验文件后缀 String originalFileName file.getOriginalFilename(); String fileExt originalFileName.substring(originalFileName.lastIndexOf(.) 1).toLowerCase(); ListString allowedExtList Arrays.asList(jpg, jpeg, png, gif, pdf, doc, docx); if (!allowedExtList.contains(fileExt)) { return 不支持的文件类型; } // 2. 校验文件内容类型MIME Type String contentType file.getContentType(); if (!contentType.startsWith(image/) !contentType.equals(application/pdf)) { // 根据业务调整 return 文件内容类型不合法; } // 3. 使用UUID重命名文件避免冲突和特殊文件名 String newFileName UUID.randomUUID().toString() . fileExt; // 4. 将文件保存到Web应用根目录之外的指定目录并通过静态资源映射访问 String saveDir /opt/app/upload_files/; // 绝对路径不在WEB-INF或Web根目录下 File dir new File(saveDir); if (!dir.exists()) dir.mkdirs(); File dest new File(saveDir newFileName); file.transferTo(dest); // 5. 返回一个无法直接执行的文件ID或映射路径通过安全的下载接口访问 String fileId generateFileId(); // 生成一个唯一ID存入数据库 fileService.saveFileInfo(fileId, newFileName, originalFileName, saveDir); return 上传成功文件ID fileId; }修复要点后缀白名单只允许业务必需的文件类型。内容校验读取文件头几个字节判断真实类型防止伪装。强制重命名使用随机名称如UUID杜绝路径穿越和覆盖。隔离存储上传文件必须保存在Web服务器无法直接解析执行的目录。通过一个安全的下载控制器Controller来读取文件并输出流在该控制器中再次进行权限校验。设置文件系统权限确保上传目录的权限最小化如755且运行Web服务的用户对该目录只有写权限对父目录无执行权限。4. 中低危漏洞与系统性问题汇总除了上述三个高危漏洞审计中还发现了一系列中低危漏洞和系统性的安全问题它们同样不容忽视组合利用可能放大风险。4.1 跨站脚本XSS漏洞不止于反射型在多个数据展示页面发现用户可控输入未经充分过滤就直接输出到HTML页面中。典型场景搜索框回显搜索功能中搜索关键词keyword被直接拼接在input value%keyword%中如果关键词包含“scriptalert(1)/script则构成存储型XSS。消息通知系统公告或用户消息内容在后台编辑时未过滤HTML标签导致存储型XSS。修复方案输入过滤在数据入库前根据上下文对特殊字符,,,“,进行HTML实体编码HtmlEncode。可以使用OWASP ESAPI或Apache Commons Text中的StringEscapeUtils.escapeHtml4()。输出编码在JSP等视图层输出时使用JSTL的c:out value${userInput}标签它默认会进行HTML编码。避免使用% %或EL表达式${userInput}直接输出。内容安全策略CSP在HTTP响应头中添加CSP策略是缓解XSS的纵深防御措施。例如Content-Security-Policy: default-src self; script-src self unsafe-inline谨慎使用unsafe-inline。4.2 不安全的直接对象引用IDOR除了用户删除的越权在订单查看、个人信息查看等接口也发现了类似问题。例如访问/order/detail?orderId1001后端仅验证用户登录未验证订单1001是否属于当前用户导致可以查看他人订单。修复方案同3.2节必须在服务端建立“用户-资源”的绑定关系校验。每个涉及资源ID访问的接口都必须查询该资源的所有者或关联权限并与当前会话用户进行比对。4.3 敏感信息泄露与配置不当错误信息泄露当应用发生SQL错误或异常时直接将详细的错误堆栈信息返回给前端暴露了数据库结构、表名、甚至部分SQL语句。修复在生产环境中配置全局异常处理器将详细的异常信息记录到日志文件而给前端返回统一的、模糊的错误提示如“系统内部错误请联系管理员”。配置文件硬编码在源码中发现数据库密码、API密钥等以明文形式写在配置文件中如jdbc.properties。修复使用环境变量、外部密钥管理服务如HashiCorp Vault或启动参数来传递敏感信息。确保配置文件本身不被提交到代码仓库。目录列表未禁用部分静态资源目录如/uploads/,/static/未禁用目录浏览功能攻击者可以直接访问目录列出所有文件。修复在Web服务器Nginx/Tomcat配置中显式关闭目录列表功能。对于Tomcat可以在web.xml中为对应Servlet设置init-paramparam-namelistings/param-nameparam-valuefalse/param-value/init-param。4.4 依赖组件已知漏洞通过检查项目的pom.xml或build.gradle文件发现其使用了存在已知高危漏洞的旧版本第三方库例如特定版本的Apache Commons Collections存在反序列化漏洞、Fastjson存在远程代码执行漏洞等。修复方案依赖扫描使用OWASP Dependency-Check、Snyk等工具对项目依赖进行自动化漏洞扫描。升级与修复根据扫描报告将存在漏洞的依赖库升级到已修复的安全版本。升级前需在测试环境充分验证兼容性。最小化依赖定期清理项目中未使用的依赖Maven的dependency:analyze减少攻击面。5. 系统性加固与安全开发建议针对本次审计暴露出的问题单纯修补单个漏洞是治标不治本。必须从开发流程和架构设计层面进行系统性加固。5.1 建立安全编码规范与强制检查为团队制定并强制执行《安全编码规范》至少包括SQL操作禁止字符串拼接SQL强制使用参数化查询PreparedStatement或安全的ORM框架方法。输入输出所有用户输入必须视为不可信进行校验和过滤所有输出到前端的数据必须根据上下文进行编码。文件操作文件路径必须标准化避免目录穿越文件上传必须遵循“白名单、重命名、非Web目录”原则。权限控制所有业务接口必须在服务端进行身份认证和细粒度授权校验。会话管理使用安全的会话机制设置合理的超时时间防止会话固定攻击。错误处理禁止向用户返回系统详细错误信息。 将安全规范纳入代码审查Code Review的必查项并利用SonarQube等工具在CI/CD流水线中设置质量门禁对违反安全规则的代码阻断合并。5.2 引入安全框架与中间件Web应用防火墙WAF在应用前端部署WAF可以有效拦截SQL注入、XSS、命令注入等常见Web攻击的已知攻击模式为应用提供第一道防线。但WAF不能替代代码本身的安全。安全开发框架如果项目基于Spring强烈建议集成Spring Security。它能提供声明式的、全面的安全控制包括认证、授权、防止CSRF、会话管理、密码加密等极大减少手动编写安全代码的工作量和出错概率。集中式权限管理对于复杂的ERP系统建议设计独立的权限服务实现基于角色RBAC或属性ABAC的访问控制模型使权限逻辑清晰、易于管理。5.3 实施常态化的安全生命周期管理安全不是一次性的审计或上线前的渗透测试而应贯穿软件生命周期SDLC。需求与设计阶段进行威胁建模Threat Modeling识别关键资产和潜在威胁。编码阶段遵循安全编码规范使用安全的API和库。测试阶段除了功能测试必须包含安全测试如SAST静态应用安全测试、DAST动态应用安全测试和定期的渗透测试。部署与运维阶段保持操作系统、中间件、数据库、依赖库的及时更新配置安全基线启用安全日志审计和监控。应急响应建立安全事件应急响应流程确保在发现漏洞或遭受攻击时能快速定位、隔离、修复和恢复。6. 修复实施与验证指南对于正在使用JshERP-2.3或类似版本的用户建议立即采取以下行动6.1 紧急修复步骤立即下线或隔离如果系统暴露在公网且尚未修复应评估风险。对于极高危漏洞如SQL注入、RCE考虑暂时将系统下线或置于内网隔离环境直至修复完成。应用补丁根据前述第3节提供的修复指南逐一对漏洞进行代码修复。优先修复高危漏洞SQL注入、RCE、越权删除。升级依赖使用工具扫描并升级所有存在已知漏洞的第三方库。修改配置检查并修正服务器、中间件、数据库的不安全配置如关闭目录列表、配置错误页面、加强数据库访问权限。6.2 修复验证方法修复完成后必须进行验证确保漏洞已被彻底堵上。SQL注入验证尝试使用‘、“、OR 11、UNION SELECT等Payload进行注入测试观察是否返回数据库错误信息或异常数据。使用Burp的Scanner模块或sqlmap进行自动化验证。越权验证使用两个不同权限的测试账号如普通用户A和管理员B。用A的会话尝试修改请求参数访问或操作本应属于B的资源如订单、用户信息。验证是否返回明确的权限错误如403或操作失败。文件上传验证尝试上传非白名单后缀的文件如.jsp,.php,.exe。尝试上传伪装成图片的WebShell修改文件Magic头。尝试在文件名中使用路径穿越序列如../../../test.jsp。上传成功后尝试直接通过Web URL访问上传的文件应返回404或下载提示而非执行。XSS验证在存在风险的输入点搜索框、编辑框输入典型的XSS Payload如scriptalert(‘XSS’)/script查看页面输出是否被正确编码显示为文本而非执行脚本。6.3 回归测试安全修复可能引入新的功能缺陷。在完成漏洞修复后必须进行全面的功能回归测试确保核心业务流程如创建订单、审批流程、财务报表生成不受影响。建议建立自动化测试用例集将安全测试用例也纳入其中便于后续持续迭代。安全之路道阻且长。对于像ERP这样的核心业务系统安全性的重要性怎么强调都不为过。本次对JshERP-2.3的深度审计揭示了许多在快速开发过程中容易被忽视的安全死角。希望这份报告不仅能帮助相关用户立即加固系统更能给广大开发者提个醒安全必须内建于开发的每一个环节从第一行代码开始。

相关新闻