路径遍历漏洞深度解析:从原理到实战修复
2026/6/25 14:34:24 网站建设 项目流程

1. 项目概述与背景

最近在梳理一些历史漏洞案例,准备给团队做一次内部的安全意识培训。翻看资料时,宏景eHR的DisplayExcelCustomReport接口任意文件读取漏洞(CNVD-2023-08743)引起了我的注意。这个漏洞本身原理不复杂,但它的出现和利用过程,非常典型地反映了在传统B/S架构企业管理软件中,开发人员对文件路径安全校验的忽视,以及攻击者如何利用这种疏忽进行信息探测和渗透。这类漏洞往往被归类为“低危”,但其实际危害,尤其是在内网环境中,可能远超预期。它就像一把能打开服务器文件系统“后窗”的钥匙,虽然不一定能直接拿到系统权限,但足以让攻击者窥探到大量敏感信息,为后续的攻击铺平道路。

这个漏洞影响的是宏景eHR人力资源管理系统。这类系统在企业内部通常存放着员工最核心的个人信息,如身份证号、薪资、家庭住址、银行账户等。一旦服务器的敏感文件被读取,后果不堪设想。复现这个漏洞,不仅能让我们理解其技术原理,更重要的是去思考:在代码审计和日常开发中,我们该如何避免犯下类似的错误,以及作为防守方,该如何快速发现和修复这类问题。接下来,我会从一个安全研究者的角度,带你一步步拆解这个漏洞,从环境搭建到漏洞利用,再到深度分析和修复建议,把整个过程掰开揉碎了讲清楚。

2. 漏洞原理深度解析

2.1 核心漏洞点:未过滤的路径穿越符

这个漏洞的核心,在于DisplayExcelCustomReport接口(通常对应一个Servlet或ASPX页面)在处理reportfile这类参数时,直接拼接了用户输入,且未对路径进行任何规范化或安全校验。攻击者可以通过输入包含路径穿越序列(如../..\)的字符串,来访问Web应用目录之外的文件。

我们来模拟一下漏洞代码可能的样子。假设后端处理逻辑是用Java写的,伪代码如下:

String userReportName = request.getParameter("report"); // 用户可控输入 String baseReportPath = "/opt/hjehr/webapps/reports/"; String fullPath = baseReportPath + userReportName + ".xls"; // 直接拼接 File reportFile = new File(fullPath); // ... 读取文件内容并输出 ...

如果攻击者传入report参数值为../../../etc/passwd,那么拼接后的fullPath就变成了/opt/hjehr/webapps/reports/../../../etc/passwd.xls。经过操作系统路径解析后,../会向上回退目录,最终实际访问的路径就变成了/etc/passwd.xls。虽然加了.xls后缀,但很多文件读取操作是二进制流读取,后缀名不影响内容获取,或者服务器配置了某些MIME类型映射,导致文件内容被直接返回。

为什么这种错误会发生?

  1. 过度信任客户端输入:开发者潜意识里认为,前端下拉框或固定链接传递过来的文件名是“安全”的,却忽略了HTTP请求可以被轻易篡改。
  2. 对路径解析机制理解不足:认为在Web目录下拼接路径就是安全的,没有意识到../这类序列会被操作系统内核解析,从而跳出Web根目录。
  3. 缺乏最小权限原则:应用可能以较高权限(如root或SYSTEM)运行,使得读取系统关键文件成为可能。

2.2 漏洞利用的影响与危害评估

很多人觉得任意文件读取(Arbitrary File Read, AFR)不如远程代码执行(RCE)危害大,这种看法是片面的。在内网渗透中,AFR往往是获取立足点的关键一步。通过这个漏洞,攻击者可以读取哪些敏感文件呢?

  • 系统配置文件/etc/passwd,/etc/shadow(Linux),c:\windows\system32\config\SAM(Windows), 用于获取系统用户哈希,尝试破解或进行哈希传递攻击。
  • 应用配置文件:数据库连接文件(如web.config,application.properties,config.php),里面通常明文或弱加密存储着数据库的IP、端口、用户名和密码。拿到数据库权限,几乎等于拿到了整个系统的数据。
  • 日志文件:应用日志、访问日志可能记录调试信息、其他用户的会话ID、甚至明文密码(如果开发不小心记录了)。
  • 源代码文件:通过读取.java,.class,.jsp,.asp等文件,可以进行白盒审计,寻找更严重的二次漏洞,如反序列化、SQL注入等。
  • 敏感数据文件:对于eHR系统,可能存在的员工信息导出文件、薪资表备份文件等。

实操心得:在实际渗透测试中,我通常会先用这个漏洞尝试读取/proc/self/cwd/../WEB-INF/web.xml(Linux)或类似路径,来定位Web应用的绝对路径和配置文件位置,这比盲目猜解要高效得多。一旦拿到数据库密码,整个内网渗透的剧本就可能完全改写。

3. 漏洞复现环境搭建与准备

3.1 环境选择与搭建要点

要复现这个漏洞,你需要一个存在漏洞的宏景eHR系统环境。强烈建议在完全隔离的虚拟机或实验网络中进行,切勿在生产环境或连接互联网的机器上尝试。

通常有两种途径获取环境:

  1. 官方历史版本安装包:通过网络资源寻找特定版本(如受漏洞影响的8.0、9.0等老版本)的安装包。安装过程可能比较复杂,涉及数据库(如SQL Server)初始化、中间件(如Tomcat、WebLogic)配置等。
  2. 漏洞靶场环境:一些在线漏洞演练平台或开源漏洞靶场项目(如Vulhub、VulnApp)可能集成了该漏洞环境。这是最快捷、最安全的方式。

这里以在本地虚拟机搭建一个简化模拟环境为例。我们不需要完整的宏景eHR,只需模拟其漏洞接口。

步骤1:创建模拟Web应用使用Spring Boot快速搭建一个模拟接口。

@RestController public class VulnController { @GetMapping("/DisplayExcelCustomReport") public void readFile(HttpServletRequest request, HttpServletResponse response) throws IOException { String reportName = request.getParameter("report"); // 模拟漏洞:直接拼接用户输入,未过滤 String filePath = "/reports/" + reportName; File file = new File(filePath); if (file.exists() && !file.isDirectory()) { Files.copy(file.toPath(), response.getOutputStream()); response.setContentType("application/octet-stream"); } else { response.getWriter().write("File not found."); } } }

将应用打包运行,其Web根目录假设为/app

步骤2:准备“敏感”文件在虚拟机中创建一些用于测试的“敏感”文件。

# Linux 环境 echo "模拟的/etc/passwd内容" > /etc/passwd_test echo "模拟的数据库配置:jdbc:mysql://localhost:3306/hjehr?user=root&password=Admin@123" > /app/WEB-INF/classes/application.properties # 在Web根目录下创建reports目录,并放一个正常文件 mkdir -p /app/reports echo "这是正常的报表内容" > /app/reports/normal.xls

注意事项

  • 确保你的Java应用有权限读取/etc/passwd_test这类系统文件(通常以root或非root用户运行,权限不同)。
  • 真实环境中,宏景eHR可能部署在Windows Server + IIS + .NET环境下,路径分隔符是\,路径穿越符是..\。原理完全相通。

3.2 工具准备

复现过程主要使用浏览器和命令行工具即可,但一些专业工具能提升效率:

  • 浏览器:Chrome或Firefox,用于手动构造URL测试。
  • Burp Suite:渗透测试必备工具。它的Repeater模块可以方便地修改和重放HTTP请求,Intruder模块可用于对路径进行模糊测试或爆破。
  • cURL:命令行下的HTTP客户端,适合脚本化测试和验证。
  • 编码工具:有时需要对特殊字符进行URL编码(如../编码为%2e%2e%2f)或双重编码,以绕过一些简单的过滤。

4. 漏洞手工复现与利用过程

4.1 基础利用:读取Web目录外文件

假设我们的模拟应用运行在http://192.168.1.100:8080

第一步:访问正常功能首先,我们访问正常的报表链接,观察其参数格式:http://192.168.1.100:8080/DisplayExcelCustomReport?report=normal这可能会成功下载/app/reports/normal.xls文件。

第二步:构造恶意参数现在,我们尝试利用路径穿越读取系统文件。将参数修改为:http://192.168.1.100:8080/DisplayExcelCustomReport?report=../../../etc/passwd_test发送这个请求。

第三步:分析结果

  • 成功情况:服务器返回了/etc/passwd_test文件的内容。响应头Content-Type可能是application/octet-stream(二进制流)或根据文件后缀猜测的类型。这直接证明了漏洞存在。
  • 失败情况:返回“File not found”或类似错误。这可能是因为:
    1. 路径计算错误:需要的../个数不对。需要根据Web根目录到目标文件的相对路径来计算。例如,如果应用实际路径是/opt/tomcat/webapps/ROOT/,要读到/etc/passwd,可能需要../../../../etc/passwd
    2. 存在基础过滤:服务器端对参数进行了一些处理,比如删除../,或限制了后缀。

技巧:如何确定穿越深度?可以采用“爬目录”的方式。先尝试读取一个已知的、位于Web应用内的文件,比如WEB-INF/web.xml(Java应用)或global.asax(.NET应用)。通过不断增减../的个数,直到成功读取到这个已知文件,就能精准定位出Web应用的绝对路径。例如:report=../../WEB-INF/web.xmlreport=../../../WEB-INF/web.xml... 一旦成功,你就知道了从参数位置到Web根目录需要回退多少层。

4.2 绕过常见过滤机制

开发人员或WAF可能会实现一些简单的过滤,我们需要尝试绕过。

场景1:过滤../字符串后端代码可能做了replace("../", "")。我们可以尝试:

  • 双写绕过..././过滤一次中间的../后,剩下的./和前面的.结合,可能又形成了../?实际上更常用的是....//,过滤掉中间的../变成..//,在某些解析中可能被当作../。但更可靠的是下面两种。
  • URL编码绕过:将../编码为%2e%2e%2f。如果过滤发生在解码前,则可能绕过。
  • 双重URL编码绕过%252e%252e%252f%被编码为%25)。如果应用进行多次解码,则可能生效。
  • 使用..\(Windows):如果服务器是Windows,尝试使用反斜杠。
  • 使用绝对路径:在某些配置错误的场景下,直接传递绝对路径如/etc/passwd也可能生效。

场景2:后缀名限制接口可能强制添加或检查.xls后缀。我们的payload是../../../etc/passwd,拼接后变成../../../etc/passwd.xls,文件不存在。

  • 空字节截断:在旧版本PHP或某些特定解析器中,%00(空字节)可以截断后面的字符串。尝试../../../etc/passwd%00.xls。但Java和.NET高版本中此方法通常无效。
  • 利用文件系统特性:在Linux下,可以尝试读取/etc/passwd/(后面加斜杠),但通常不行。更有效的方法是,目标文件可能本身就有.xls,.xml,.properties等后缀,如我们之前创建的application.properties。尝试读取../../../WEB-INF/classes/application.properties,即使加上.xls后缀,因为文件真实存在,也可能被读取。

实操过程记录(使用Burp Suite):

  1. 浏览器正常访问,用Burp抓包。
  2. 将请求发送到Repeater。
  3. 修改report参数,依次尝试以下payload,观察响应长度和内容的变化:
    ../../../etc/passwd_test ..%2f..%2f..%2fetc%2fpasswd_test ..\..\..\windows\system32\drivers\etc\hosts (Windows路径) ../../WEB-INF/web.xml ....//....//....//etc/passwd_test
  4. 如果响应体变长,且内容包含预期的文本(如“root:x:0:0”或“jdbc:mysql”),则说明利用成功。

5. 漏洞深入利用与信息收集

成功实现任意文件读取后,攻击就进入了“信息收集”阶段。目标是找到能进一步突破的“钥匙”。

5.1 关键敏感文件定位清单

以下是一张针对常见系统的敏感文件读取清单,在实际测试中可按顺序尝试:

系统平台文件路径可能包含的敏感信息
Linux/Unix/etc/passwd系统用户列表,可用于用户名枚举
/etc/shadow用户密码哈希(需root权限)
/proc/self/environ当前进程环境变量,可能含路径、密钥
/proc/self/cmdline启动当前进程的命令行,含完整路径
/home/[用户名]/.bash_history用户历史命令,可能含密码、密钥
/root/.ssh/id_rsaroot用户的SSH私钥
[WEBROOT]/WEB-INF/web.xmlJava Web应用配置,含数据库连接等
[WEBROOT]/config/database.phpPHP应用数据库配置
WindowsC:\Windows\System32\config\SAM本地用户账户数据库(需SYSTEM权限)
C:\boot.ini系统启动配置(旧系统)
C:\Windows\win.ini系统基础配置
C:\Windows\System32\inetsrv\config\applicationHost.configIIS配置文件,含站点路径、权限
[WEBROOT]\web.config.NET应用配置文件,含连接字符串
C:\Users\[用户名]\Desktop\passwords.txt用户桌面文件(猜解用户名)
通用/应用相关[WEBROOT]/WEB-INF/classes/application.propertiesSpring Boot配置
[WEBROOT]/META-INF/context.xmlTomcat数据源配置
[WEBROOT]/config.json各种前端/Node.js应用配置
../logs/error.log应用错误日志,含堆栈跟踪、SQL语句
../备份文件.zip可能存在的数据库备份文件

注意:读取/proc/self/mem/dev/mem等内存设备文件通常会导致进程卡死或服务器负载飙升,在测试中应避免。

5.2 从信息泄露到权限提升

假设我们通过漏洞成功读取到了WEB-INF/classes/application.properties,内容如下:

spring.datasource.url=jdbc:mysql://192.168.1.200:3306/hjehr_prod spring.datasource.username=hjehr_user spring.datasource.password=HJ@Ehr#2023Prod!

攻击链就此延伸:

  1. 数据库渗透:直接使用得到的凭据连接内网数据库服务器(192.168.1.200)。可以导出所有员工数据,甚至尝试通过数据库的特定功能(如MySQL的INTO OUTFILE)向Web目录写入木马,获取Webshell。
  2. 横向移动:数据库密码往往在其他系统中复用。可以尝试用此密码登录管理后台、VPN、邮箱等系统。
  3. 代码审计:读取到的Java类文件(.class)可以通过反编译工具(如JD-GUI)查看源码,寻找更深的漏洞,如SQL注入、反序列化点等。

我踩过的坑:有一次测试中,读取到的配置文件里密码是加密的。不要轻易放弃,常见的加密方式如AES、DES,密钥可能硬编码在代码里。继续读取相关的ConfigUtil.class等文件,反编译后可能找到解密逻辑,从而还原出明文密码。

6. 漏洞修复方案与安全开发建议

6.1 临时缓解与根本修复

临时缓解措施(运维层面):

  1. WAF规则:在Web应用防火墙(WAF)或网关设备上,添加规则拦截请求参数中包含../..\%2e%2e等路径穿越序列的请求。
  2. 权限最小化:修改运行Web服务的系统账户(如Tomcat的tomcat用户),确保其只有Web应用目录及必要资源的读取权限,无法读取/etc//windows/等系统目录。
  3. 虚拟补丁:如果使用的是Java Servlet过滤器或.NET HTTP模块,可以在请求到达漏洞接口前,对相关参数进行过滤和拒绝。

根本修复方案(开发层面):修复的核心原则是:对用户输入的文件名部分进行严格的白名单校验,并规范化最终路径,确保其位于允许的目录内。

Java示例(修复后):

public void readFile(HttpServletRequest request, HttpServletResponse response, String reportParam) throws IOException { String userReportName = request.getParameter(reportParam); // 1. 白名单校验:只允许字母、数字、下划线、短横线组成的文件名 if (!userReportName.matches("^[a-zA-Z0-9_-]+\\.xls$")) { // 假设后缀必须是.xls response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid report name."); return; } // 2. 定义安全的基准目录 Path safeBaseDir = Paths.get("/opt/hjehr/webapps/reports/").normalize().toAbsolutePath(); // 3. 拼接用户输入,并立即规范化 Path userPath = Paths.get(userReportName).normalize(); // 注意:这里不要使用`resolve`,因为如果userPath是绝对路径,会替换掉safeBaseDir Path resolvedPath = safeBaseDir.resolve(userPath).normalize().toAbsolutePath(); // 4. 关键安全校验:确保最终路径在基准目录之下 if (!resolvedPath.startsWith(safeBaseDir)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied."); return; } File reportFile = resolvedPath.toFile(); // ... 后续文件操作 ... }

关键点解释:

  • normalize():方法会移除路径中的...,解析符号链接(取决于参数)。例如,/reports/../etc/passwd经过normalize()后会变成/etc/passwd。我们在校验startsWith之前调用它,就能让../失效。
  • startsWith():这是最核心的检查,确保最终要访问的文件路径,其前缀是我们指定的安全基准目录。任何试图跳出该目录的尝试都会被拦截。
  • 白名单优先:如果业务上文件名是固定的几个,使用白名单(如从数据库已发布的报表列表中校验)比黑名单(过滤../)要安全得多。

6.2 安全开发规范建议

为了避免此类漏洞,团队应在开发流程中嵌入安全要求:

  1. 输入校验:对所有用户输入的、用于文件系统操作的参数(文件名、路径),实施严格的白名单校验。
  2. 路径安全API:使用安全的API进行路径操作。在Java中,使用java.nio.file.Path及其normalize()startsWith()方法;在Python中,使用os.path.normpath()后检查前缀。
  3. 上下文传递:尽量避免在参数中传递文件路径。使用文件ID、索引等标识符,在服务端通过映射表获取真实路径。
  4. 代码审计:将“路径遍历”作为代码审计的必查项。重点关注File,FileInputStream,Paths.get,include,require等函数或指令的参数是否用户可控。
  5. 安全培训:让开发人员理解路径遍历漏洞的原理和危害,而不仅仅是记住要过滤../

7. 防御视角下的检测与响应

7.1 如何发现环境中的此类漏洞

作为防守方(蓝队),除了定期进行漏洞扫描,还可以通过以下方式主动发现:

  • 日志分析:在Web访问日志中,搜索包含../..\%2e%2e等特征的URL请求。这些很可能是攻击者的探测行为。
  • HIDS监控:部署主机入侵检测系统,监控Web服务进程(如java, tomcat, w3wp.exe)对敏感文件(如/etc/shadow,web.config)的读取操作。一旦发现异常读取,立即告警。
  • RASP防护:在应用层部署运行时应用自我保护,可以精准地拦截对FileInputStream等类的危险调用,当检测到参数包含路径穿越并试图跳出安全目录时,实时阻断并记录。

7.2 事件应急响应流程

如果通过监控发现疑似利用该漏洞的攻击,应立刻启动应急响应:

  1. 隔离:如果可能,暂时隔离受影响的主机或网络段。
  2. 取证:保存完整的攻击请求日志、服务器上的相关文件访问记录。
  3. 评估:根据攻击者读取的文件,评估信息泄露的范围和严重程度(如是否读到数据库密码)。
  4. 修复:按照上述方案紧急修复漏洞。
  5. 溯源:检查攻击IP、攻击路径,看是否已发生进一步入侵(如数据库被连接)。
  6. 通知:如果确认泄露了用户个人信息,需根据相关法律法规启动通知流程。
  7. 加固:全面检查其他系统是否存在类似问题,并实施权限最小化等加固措施。

这个漏洞的复现和分析过程,再次印证了安全领域那句老话:“漏洞往往出现在最意想不到的简单逻辑里。” 作为开发,写完代码后多问一句“这个参数用户完全可控吗?”;作为运维,部署系统时多思考“这个账户真的需要这么高的权限吗?”,很多风险其实就能被扼杀在萌芽状态。安全不是某个阶段的任务,而应该是贯穿整个软件生命周期的一种思维方式。

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

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

立即咨询