1. 项目概述与背景最近在梳理一些开源WMS仓库管理系统的历史漏洞时JeeWMS这个项目引起了我的注意。这是一个基于Java开发的、功能相对完整的开源仓库管理系统在不少中小型企业的内部物流管理中有所应用。安全研究人员在审计其代码时在graphReportController.do这个接口中发现了一个典型的SQL注入漏洞并被分配了CVE编号CVE-2025-0392。这类漏洞的根源往往在于开发者对用户输入数据的信任过度直接将前端参数拼接到了SQL语句中而没有经过严格的预处理或过滤。对于安全从业者、渗透测试工程师甚至是开发人员来说理解这类漏洞的成因、掌握其复现方法并最终推导出修复方案是提升自身安全能力的关键一步。今天我就以一个“攻击者”兼“防御者”的双重视角带大家完整地走一遍这个漏洞的复现与分析过程希望能给正在学习Web安全或从事企业安全建设的你提供一份可以直接上手操作的实战参考。2. 漏洞环境搭建与准备2.1 目标系统与漏洞组件定位要复现一个漏洞首先得有一个靶场。JeeWMS是一个开源项目我们可以直接从其官方仓库或镜像站点下载存在漏洞的历史版本。根据漏洞披露信息CVE-2025-0392影响的是特定版本的graphReportController.do接口。因此我们的第一步是搭建一个可用的、存在漏洞的JeeWMS测试环境。我选择在本地虚拟机中使用Docker进行部署这样环境隔离性好清理起来也方便。首先我们需要找到JeeWMS的Docker镜像或者其发布包。经过搜索我找到了一个包含MySQL和Tomcat的集成环境包。下载解压后其目录结构通常包含数据库初始化脚本sql/目录、Web应用War包webapps/目录以及一些配置文件。注意请务必在授权的测试环境如本地虚拟机、隔离的VPS或专门的靶场平台中进行所有操作。未经授权对任何线上系统进行测试都是非法且不道德的。部署过程大致如下启动MySQL服务执行初始化SQL脚本创建数据库和表结构然后配置Tomcat将WAR包部署到webapps目录下并启动Tomcat服务。确保应用能正常访问登录页面并使用默认账号如admin/admin123成功登录这标志着基础环境已经就绪。2.2 测试工具与必要配置工欲善其事必先利其器。对于SQL注入漏洞的检测与利用我们主要需要以下几类工具浏览器与开发者工具现代浏览器如Chrome、Firefox自带的开发者工具F12是基础。我们将用它来观察网络请求、修改参数、查看响应。特别是“网络”Network标签页能清晰地看到每个请求的URL、参数、方法GET/POST和响应内容。代理抓包工具Burp Suite是渗透测试的瑞士军刀社区版就足够我们使用。我们需要将其设置为系统的代理通常是127.0.0.1:8080并让浏览器流量经过它。这样我们可以拦截、查看、修改和重放任何一个HTTP/HTTPS请求这对于手动测试和漏洞利用至关重要。漏洞扫描/利用辅助工具虽然手动测试能加深理解但像sqlmap这样的自动化工具能极大提高效率。它是一个开源的渗透测试工具可以自动检测和利用SQL注入漏洞并获取数据库数据。我们将用它来验证我们的手动发现并尝试进行更深度的利用。数据库连接工具为了验证我们注入获取的数据是否正确或者理解后端数据库结构一个能直连目标数据库的工具如MySQL Workbench、Navicat或命令行mysql客户端会很有帮助。当然这需要我们知道数据库的连接信息通常在应用的配置文件中。在开始前请确保Burp Suite已正确安装并配置了浏览器代理同时准备好sqlmap。我们将采用“手动探测 - 工具验证 - 深度利用”的流程来展开。3. 漏洞原理与入口点分析3.1 漏洞接口定位与功能理解漏洞的入口点是graphReportController.do这个接口。从命名上看这很可能是一个用于生成或获取图表报表数据的控制器Controller。在基于Java的Web框架如Struts2、Spring MVC中以.do结尾的URL通常映射到特定的处理类和方法。我们的第一步是找到应用中使用到这个接口的地方。通过浏览JeeWMS的前端页面特别是那些带有数据图表、统计报表的模块同时用Burp Suite抓取背景的Ajax请求我们很快就能定位到具体的请求。例如可能在“库存报表”、“出入库统计”等页面前端会向/jeewms/graphReportController.do?methodxxx这样的地址发送请求并携带查询参数。假设我们找到了一个请求如下GET /jeewms/graphReportController.do?methodgetChartDatareportId123startDate2023-01-01endDate2023-12-31这里reportId,startDate,endDate就是可能的用户输入参数。漏洞的根源就在于后端Java代码在处理这些参数时可能采用了不安全的字符串拼接方式来构造SQL语句。3.2 SQL注入漏洞成因深度解析为什么字符串拼接SQL语句是危险的我们来看一个简化的、模拟漏洞的代码片段注此为根据常见漏洞模式还原的示意代码非原厂源码// 危险示例字符串拼接 public String getChartData(String reportId, String startDate, String endDate) { String sql SELECT * FROM inventory_report WHERE report_id reportId AND create_time BETWEEN startDate AND endDate ; // 直接执行sql... return executeQuery(sql); }在这段代码中reportId、startDate、endDate这三个参数被直接拼接到SQL字符串中。如果攻击者控制reportId参数将其值设置为123 OR 11那么最终生成的SQL语句将变成SELECT * FROM inventory_report WHERE report_id 123 OR 11 AND create_time BETWEEN 2023-01-01 AND 2023-12-31由于11这个条件永远为真True这条SQL语句很可能会返回inventory_report表中的所有数据而不仅仅是report_id为123的那一条。这就是一个典型的“永真条件”注入它绕过了原有的查询逻辑。更危险的是如果后端数据库支持多语句执行如MySQL在某些配置下攻击者甚至可以通过注入点执行任意SQL命令例如在参数后添加; DROP TABLE users; --。--是SQL中的注释符用于注释掉原SQL语句后面的部分使得注入的破坏性语句能独立执行。安全的做法应该是使用预编译语句PreparedStatement配合参数化查询。预编译语句会将SQL语句的模板带有占位符?先发送给数据库编译然后将用户输入的数据作为参数单独传递。数据库能清晰地区分“指令”和“数据”从而从根本上杜绝了将用户输入解释为SQL指令的可能性。// 安全示例使用PreparedStatement public String getChartData(String reportId, String startDate, String endDate) { String sql SELECT * FROM inventory_report WHERE report_id ? AND create_time BETWEEN ? AND ?; PreparedStatement pstmt connection.prepareStatement(sql); pstmt.setString(1, reportId); pstmt.setString(2, startDate); pstmt.setString(3, endDate); // 执行查询... return pstmt.executeQuery(); }JeeWMS的这个漏洞正是因为在graphReportController.do接口的某个方法中缺失了这种安全的编程实践。4. 手动漏洞探测与验证4.1 初步探测与注入点确认理论清晰后我们开始实战。首先用浏览器正常访问一个图表页面并用Burp Suite拦截对应的graphReportController.do请求。假设我们拦截到的请求是GET /jeewms/graphReportController.do?methodqueryid1typemonthly我们的怀疑对象是id和type参数。手动测试SQL注入的第一步通常是尝试触发一个“异常”。单引号测试将id参数的值改为1。发送请求后观察响应。如果页面返回了数据库错误信息如MySQL的“You have an error in your SQL syntax...”或者页面显示空白、与正常响应迥异这强烈暗示该参数可能存在注入点并且错误信息未被妥善处理。逻辑测试永真条件尝试id1 OR 11。如果页面返回了更多数据例如原本只显示一条报表现在显示了多条或所有报表则进一步确认了注入。永假条件尝试id1 AND 12。这是一个永远为假的条件如果页面返回空数据或与id999一个不存在的ID类似的结果也是注入存在的一个旁证。注释符测试尝试id1--注意--后有一个空格在URL中常编码为--%20。注释符用于“注释”掉原SQL语句中后续的部分。如果注入成功这个请求的响应应该与id1的正常请求完全一致因为后的条件比如AND typemonthly被注释掉了。在我的测试中对id参数使用1时页面返回了一个包含Java堆栈跟踪的错误页面其中明确提到了SQL语法错误这几乎就是漏洞存在的“铁证”。4.2 判断数据库类型与注入类型确认存在注入点后我们需要判断后端数据库的类型和注入的类别字符型还是数字型这决定了后续利用的Payload构造方式。数据库类型判断通过注入特定的“数据库函数”并观察响应差异来判断。尝试id1 AND sleep(5)--。如果页面响应延迟了大约5秒那么数据库很可能是MySQL因为sleep()是MySQL的函数。尝试id1 AND (SELECT COUNT(*) FROM information_schema.tables)0--。如果请求正常或返回特定信息也指向MySQL因为information_schema是MySQL的系统数据库。对于Oracle可以尝试id1 AND (SELECT * FROM dual) IS NOT NULL--。对于SQL Server可以尝试id1 AND version0--。 根据错误信息中提到的表名、函数名也能辅助判断。JeeWMS默认使用MySQL因此我们的后续利用将基于MySQL进行。注入类型判断数字型注入如果参数在SQL中直接作为数字使用如WHERE id 1那么注入时可能不需要闭合单引号。测试id1 AND 11和id1 AND 12观察逻辑差异。字符型注入如果参数在SQL中被单引号包裹如WHERE id 1就是我们目前遇到的情况需要先用闭合前面的引号然后注入我们的Payload最后可能还需要处理原SQL语句剩下的部分用注释符--或#。 从之前的单引号测试触发错误来看这明显是一个字符型注入。5. 利用SQLMap进行自动化验证与信息获取手动验证成功后我们可以使用sqlmap进行更高效、更深入的利用。sqlmap能自动化完成从检测、枚举数据库信息到最终拖取数据的全过程。5.1 基本检测与数据库信息枚举首先我们将Burp Suite拦截到的请求包括Cookie等完整头部保存到一个文本文件中比如request.txt。然后使用sqlmap加载这个文件进行测试。sqlmap -r request.txt --batch --risk3 --level3-r request.txt: 从文件加载HTTP请求。--batch: 以非交互模式运行所有提示都选择默认值。--risk3: 设置风险等级为3最高会尝试更多危险的Payload如基于时间的盲注。--level3: 设置测试等级为3会测试更多的参数和HTTP头部如Cookie、User-Agent。运行后sqlmap会快速识别出注入点并询问是否要跳过其他参数的测试。我们选择是。很快它会输出检测结果确认漏洞存在并识别出后端数据库是MySQL。接下来我们可以枚举数据库的基本信息sqlmap -r request.txt --dbs这条命令会列出所有可访问的数据库。通常我们会看到information_schema、mysql等系统库以及JeeWMS的应用数据库可能叫jeewms、wms等。确定目标数据库后枚举其中的表sqlmap -r request.txt -D jeewms --tables这里-D jeewms指定了目标数据库。命令执行后我们会看到该数据库下的所有表名例如sys_user用户表、sys_role角色表、wms_inventory库存表等。对于渗透测试sys_user这类存储凭证的表通常是首要目标。5.2 提取敏感数据与深入利用获取表名后我们可以进一步提取表的列结构和数据。枚举表字段sqlmap -r request.txt -D jeewms -T sys_user --columns这条命令会列出sys_user表的所有列名我们很可能会看到id、username、password、email、salt等字段。拖取表数据sqlmap -r request.txt -D jeewms -T sys_user -C username,password,salt --dump-C指定要提取的列--dump会将数据转储到本地。sqlmap执行后我们会得到所有用户的用户名、密码哈希值和盐salt。JeeWMS的密码通常采用MD5加盐哈希的方式存储格式可能是md5(md5(password) salt)。破解密码哈希获取到哈希值后可以尝试在线破解网站如cmd5.com或使用本地工具如hashcat进行破解。如果密码强度不高有很大几率被破解从而获得后台管理权限。尝试获取操作系统权限在极少数配置不当的情况下如果数据库用户具有FILE_PRIV权限甚至可以通过SQL注入写入Webshell。例如利用SELECT ... INTO OUTFILE语句将一段PHP或JSP代码写入Web目录。但这一步风险极高且成功率依赖于严格的配置条件在实际授权测试中需非常谨慎并明确测试范围。重要提示在真实的渗透测试或安全评估中数据提取的深度和范围必须严格遵守授权书Rules of Engagement的规定。未经明确授权禁止提取业务数据、用户隐私信息等敏感内容。6. 漏洞修复方案与安全编码实践复现漏洞的最终目的是为了修复和预防。针对CVE-2025-0392这类SQL注入漏洞修复方案是明确且直接的。6.1 立即修复方案参数化查询开发团队需要立即定位到graphReportController.do对应的Java代码文件找到存在漏洞的SQL执行语句。将所有的字符串拼接方式替换为使用PreparedStatement进行参数化查询。这是唯一被证明能有效防御SQL注入的方法。修复示例代码对比前面已经给出。关键在于确保项目内所有执行SQL的地方都进行此类改造包括使用ORM框架如MyBatis时也必须使用#{}参数占位符而非${}字符串替换。6.2 辅助防御与最佳实践除了核心修复还应建立多层防御体系输入验证与过滤在参数进入业务逻辑前进行严格的类型、长度、格式校验。例如reportId如果应该是数字就强制转换为整数类型日期参数应匹配特定的正则表达式。但请注意输入验证不能替代参数化查询它只是增加了一道防线。最小权限原则为Web应用连接数据库的账户分配最小必要的权限。通常只授予其对应数据库的SELECT、INSERT、UPDATE、DELETE权限绝对不要授予DROP、FILE、GRANT OPTION等高级权限。这能在漏洞被利用时将损失降到最低。错误信息处理避免将详细的数据库错误信息直接返回给前端用户。应配置全局异常处理器将生产环境中的异常转换为通用的错误提示页面防止攻击者从错误信息中获取数据库结构等敏感信息。Web应用防火墙WAF在应用前端部署WAF可以拦截常见的SQL注入攻击Payload为修复漏洞争取时间。但WAF可能存在被绕过的风险不能作为根本解决方案。定期安全审计与代码扫描将静态应用程序安全测试SAST工具集成到CI/CD流程中自动检测代码中的安全漏洞。同时定期对系统进行动态应用程序安全测试DAST和渗透测试。7. 复现过程中的常见问题与排查技巧在实际复现过程中你可能会遇到一些“坑”。这里记录了几个我踩过的和常见的问题环境启动失败数据库连接不上。排查首先检查MySQL服务是否正常运行systemctl status mysql。其次检查JeeWMS的数据库配置文件如jdbc.properties确认数据库连接URL、用户名、密码是否正确。最后查看Tomcat启动日志catalina.out或logs目录下的文件里面通常有详细的错误信息。使用sqlmap测试时始终无法检测到注入点。排查确认代理设置确保Burp Suite代理开启且浏览器/system代理设置正确sqlmap命令中使用了-r加载了正确的、包含完整Cookie的请求文件。检查参数位置有时漏洞不在最明显的id参数而在type或其他隐藏参数。尝试用sqlmap的-p参数指定测试某个参数如sqlmap -r request.txt -p type。尝试POST请求如果请求是POST方式确保request.txt文件中包含了POST数据体。调整level和risk提高--level和--risk值让sqlmap进行更深入的测试。手动验证回归手动测试用单引号、永真永假逻辑确认漏洞是否存在。可能应用有简单的过滤需要尝试不同的绕过技巧。可以注入但sqlmap无法枚举数据库或表。排查这可能是由于当前数据库用户权限较低无法访问information_schema数据库。可以尝试使用--current-db查看当前数据库或者尝试用--sql-shell参数获取一个交互式的SQL shell手动执行查询。也可能是WAF或应用防火墙拦截了sqlmap的探测Payload可以尝试使用--tamper脚本对Payload进行编码或混淆如space2comment。获取到的密码哈希无法破解。排查首先确认哈希算法。JeeWMS常见的是md5(md5(password)salt)。你需要将“密码”和“盐”按照这个顺序拼接后计算MD5再与数据库中的哈希值对比。可以使用在线工具或编写简单的Python脚本进行批量验证。如果密码本身足够复杂长随机字符串那么破解是不现实的这时应关注漏洞本身而非密码强度。整个复现过程从环境搭建到手动验证再到工具利用最后到修复建议是一个完整的闭环。对于安全研究者它提供了漏洞分析的实战案例对于开发者它是一次深刻的安全意识教育。记住安全是一个持续的过程而非一劳永逸的状态。将安全编码规范内化为开发习惯配合定期的检查和评估才能构筑起应用系统的坚实防线。