Pikachu靶场通关笔记(45) — SSRF漏洞 (file_get_contents篇)

52次阅读
没有评论

1. 漏洞概述

在上一篇笔记中,我们探讨了由 PHP curl 扩展配置不当引起的 SSRF 漏洞。而在 PHP 开发中,还有一个极其常用且容易引发 SSRF 的内置函数,那就是 file_get_contents()

很多新手开发者以为 file_get_contents() 只能用来读取本地磁盘上的文本文件。但实际上,只要 PHP 配置文件 php.ini 中的 allow_url_fopen 选项处于开启状态(默认通常开启),这个函数就 支持读取远程 URL 的内容 ,并且 支持各种强大的 PHP 伪协议

如果开发者没有对传入该函数的文件路径或 URL 进行严格过滤,攻击者同样可以利用它发起 SSRF 攻击,实现内网探测、外网代理以及任意文件读取。


2. 闯关实操 (Exploitation)

第一步:发现输入点

打开关卡,点击页面上的超链接“反正都读了, 那就在来一首吧”。观察浏览器地址栏,发现参数从上一关的 url 变成了 file
http://localhost:8899/vul/ssrf/ssrf_fgc.php?file=http://127.0.0.1/.../info2.php
页面成功回显了一首诗,证明后台正在根据 file 参数的内容去获取数据。

第二步:HTTP/HTTPS 外网请求测试

我们直接将参数替换为外网地址,测试服务器是否能作为代理:
http://localhost:8899/vul/ssrf/ssrf_fgc.php?file=https://www.baidu.com
回车后,页面成功展示了百度首页!这证实了 file_get_contents 函数支持解析外网 URL,SSRF 漏洞确实存在。

Pikachu 靶场通关笔记(45)— SSRF 漏洞 (file_get_contents 篇)

第三步:任意文件读取 (绝对路径与 file:// 协议)

curl 不同的是,file_get_contents 天生就是用来读文件的。所以我们甚至不需要加协议头,直接输入绝对路径就能读取 Linux 系统的敏感文件:
http://localhost:8899/vul/ssrf/ssrf_fgc.php?file=/etc/passwd
(注:规范的 SSRF 测试中,依然推荐使用 file:///etc/passwd 以明确指定伪协议)
请求发送后,页面上直接输出了系统的用户账号信息。

Pikachu 靶场通关笔记(45)— SSRF 漏洞 (file_get_contents 篇)

第四步:学以致用,内网 PHP 探针执行

在上一关的测试中,我们在 vul/ssrf/upload/ 目录下留下了一个包含 <?php phpinfo(); ?>1.php 文件。当时因为 Docker 端口映射问题踩了坑。
这次我们学聪明了,因为代码在容器内部执行,所以直接请求容器内部的 80 端口(去掉外部的 8899):
http://localhost:8899/vul/ssrf/ssrf_fgc.php?file=http://localhost/vul/ssrf/upload/1.php
页面上成功回显了壮观的 PHP 版本信息表格。

Pikachu 靶场通关笔记(45)— SSRF 漏洞 (file_get_contents 篇)

【进阶补充】:利用 php://filter 伪协议读取源码

如果你注意看官方的源码注释,会发现一个高级玩法。如果我们直接读取 .php 文件,PHP 引擎会解析它,我们看到的是执行后的结果(比如 phpinfo 的表格)。那如果我想 查看 PHP 文件的源代码 怎么办?
我们可以使用 php://filter 伪协议,将源码以 base64 编码的形式读取出来,防止其被执行:
?file=php://filter/read=convert.base64-encode/resource=ssrf_fgc.php
(将其 base64 解码后,就能看到后端的 PHP 源码了,这是实战中窃取数据库配置文件密码的常用手段)

Pikachu 靶场通关笔记(45)— SSRF 漏洞 (file_get_contents 篇)
Pikachu 靶场通关笔记(45)— SSRF 漏洞 (file_get_contents 篇)

3. 源码分析 (Code Review)

来看一下导致本次漏洞的后端 PHP 代码:

// 读取 PHP 文件的源码:php://filter/read=convert.base64-encode/resource=ssrf.php
// 内网请求:http://x.x.x.x/xx.index
if(isset($_GET['file']) && $_GET['file'] !=null){$filename = $_GET['file'];

    // 核心漏洞点:直接将前端传入的值带入 file_get_contents
    $str = file_get_contents($filename);

    echo $str;
}

漏洞成因非常简单粗暴:
程序接收到 $_GET['file'] 后,直接赋给 $filename 变量,然后 毫无防备 地丢进了 file_get_contents() 函数中,并将获取到的结果 $str 直接 echo 输出。
因为没有任何的白名单限制、协议限制或目录限制,攻击者可以通过传入恶意的 URL 或伪协议,让服务器变成任人摆布的提线木偶。


4. 修复建议

防御由 file_get_contents() 引起的 SSRF 或任意文件读取漏洞,可以采取以下措施:

  1. 关闭不必要的配置(治本方案之一)
    如果你的业务明确只需要读取本地磁盘文件,不需要去网上抓取图片或调用外部 API,强烈建议在 php.ini 中关闭 allow_url_fopen
    allow_url_fopen = Off
    关闭后,file_get_contents 就只能读本地文件,无法发起 HTTP/HTTPS 网络请求。
  2. 严格的白名单校验(推荐)
    不要让用户输入完整的 URL 或路径。前端只传递一个 ID,后端通过数组映射真实路径。
    如果业务确实需要用户输入外部 URL,必须解析 URL 的 Host 并在白名单中进行严格比对,同时拦截对内网 IP(如 127.0.0.1, 192.168.x.x)的请求。
  3. 限定协议
    在处理之前,使用正则或字符串匹配,强制要求输入必须以 http://https:// 开头,杜绝 file://, php:// 等伪协议的滥用。
  4. 固定访问目录(防本地文件读取)
    如果业务是读取本地文件,可以在代码中写死基础路径,并过滤掉用户输入中的 ../ 目录跳转符:
    php $base_dir = "/var/www/html/safe_dir/"; $file = basename($_GET['file']); // 过滤掉所有路径信息,只保留文件名 $content = file_get_contents($base_dir . $file);
正文完
 0
评论(没有评论)