Pikachu靶场通关笔记(39) — 目录遍历 (Directory Traversal)

20次阅读
没有评论

1. 漏洞概述

目录遍历漏洞(Directory Traversal),也常被称为路径遍历(Path Traversal)。当应用程序接收用户输入的参数来决定读取哪个文件时,如果没有对用户输入进行严格的安全过滤,攻击者就可以通过输入特殊的目录跳转字符(如 ../..\),跳出程序预设的目录限制。

攻击者利用这个漏洞,可以访问系统上的任意文件,读取敏感信息(如系统密码文件 /etc/passwd、配置文件、源代码等),如果结合其他漏洞甚至可能导致服务器被完全控制。


2. 闯关实操 (Exploitation)

第一步:页面功能观察与参数发现

打开关卡页面,主界面上显示了两篇小短文的链接:

  • we're jarheads!
  • Truman's word!

我们点击第一个链接,观察浏览器顶部地址栏的变化。发现 URL 变为了:
http://localhost:8899/vul/dir/dir_list.php?title=jarheads.php

这里出现了一个非常明显的传参点:title=jarheads.php。系统似乎是通过 title 这个参数来决定页面展示哪个 PHP 文件的内容。

第二步:Payload 构造与测试

既然 title 接收的是一个文件名,我们自然会想到:如果改变 title 后面的变量值,尝试使用相对路径去访问其他目录的文件,系统会作何反应?

我们可以使用典型的 Linux 敏感文件路径 /etc/passwd 来进行测试。为了跳出当前的 soup/ 目录,我们需要在文件名前加上多个 ../(返回上一级目录)。

我们使用 HackBar 或直接修改 URL,将参数修改为:
title=../../../../etc/passwd
(注:../ 的数量取决于当前文件所在的目录深度,多写几个通常没有影响,因为到达根目录 / 后继续 ../ 依然是根目录。)

第三步:漏洞利用成功

敲击回车发送请求后,页面上赫然打印出了 Linux 系统的用户账号信息(如 root:x:0:0:root:/root:/bin/bash 等)。这证明了目录遍历漏洞确实存在,我们成功跨越了系统目录读取了敏感数据!

Pikachu 靶场通关笔记(39) — 目录遍历 (Directory Traversal)

3. 源码分析 (Code Review)

为什么这么轻易就被遍历了?我们来看一下后端的 PHP 源码:

$html='';
if(isset($_GET['title'])){$filename=$_GET['title'];
    // 这里直接把传进来的内容进行了 require(), 造成问题
    require "soup/$filename";
    //  echo $html;
}

漏洞成因非常直接:

  1. 程序通过 $_GET['title'] 获取用户输入,并直接赋值给变量 $filename
  2. 毫无防备 地将拼接后的路径 "soup/$filename" 直接交给了 PHP 的 require 函数执行。
  3. 程序没有对 $filename 进行任何过滤,没有检查里面是否包含 ../ 等非法字符,也没有限制文件包含的路径范围。

当我们将 $filename 传入 ../../../../etc/passwd 时,底层执行的其实是:
require "soup/../../../../etc/passwd";
这就相当于直接 require "/etc/passwd",从而把文件内容直接输出到了前端页面。

(注意:这里使用的是 require,它不仅能读取文件内容,如果读取的文件中包含 PHP 代码,还会当作 PHP 代码执行,所以这里本质上也构成了一个本地文件包含漏洞 LFI)。


4. 修复建议

防御目录遍历漏洞的核心思路是:永远不要信任用户的输入,并且切断用户输入与文件系统底层 API 的直接交互。

针对本关卡的修复建议如下:

  1. 使用白名单机制(推荐)
    如果能够确定用户可以访问的文件列表,最好的办法是写死一个数组映射。比如:传入 id=1,后端代码对应包含 jarheads.php,传入其他值一律拒绝。避免用户直接输入文件名。
  2. 过滤危险字符
    如果一定要传递文件名,必须过滤掉 ./\ 等目录跳转字符。
  3. 使用 basename() 函数
    basename() 函数会截取路径中的最后一部分(即纯文件名),从而彻底消除掉 ../ 这种路径穿越企图。// 修复示例 if(isset($_GET['title'])){// 使用 basename 提取纯文件名,丢弃任何路径信息 $filename = basename($_GET['title']); $filepath = "soup/" . $filename;// 最好再加一层文件是否存在的判断 if(file_exists($filepath)){require $filepath;} else {echo "文件不存在!";}}
正文完
 0
评论(没有评论)