反序列化漏洞
PHP反序列化漏洞也叫PHP对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。反序列化漏洞并不是PHP特有,也存在于Java、Python等语言之中,但其原理基本相通。
反序列化函数:
serialize():将一个对象转成字符串形式,方便保存以便于下次再次反序列化出该对象直接使用。
unserialize():将序列化后的字符串反序列化成一个对象。
序列化实例学习
简单示例:
1 <?php 2 class Test{ 3 public $name; 4 public $blog; 5 } 6 7 $test = new Test(); 8 $test->name = "007fly"; 9 $test->blog = "https://www.cnblogs.com/mysky007/"; 10 echo "创建对象并给其属性赋值:<br>"; 11 foreach($test as $key => $value) { 12 echo $key." => ".$value."<br>"; 13 } 14 15 $str = serialize($test); 16 echo "<br>对象序列化后的字符串:".$str; 17 $f = fopen('test.txt', 'w'); 18 fwrite($f, $str); 19 fclose($f); 20 21 ?>
运行后,可以看到创建的对象序列化后的字符串:
test2.php
1 <?php 2 $f = fopen("test.txt", "r"); 3 $str = fread($f, filesize("test.txt")); 4 echo "读取序列化的字符串:".$str; 5 echo "<br><br>经过反序列化后的结果如下:<br>"; 6 $test = unserialize($str); 7 foreach($test as $key => $value) { 8 echo $key." => ".$value."<br>"; 9 } 10 11 ?>
读取序列化的字符串:O:4:"Test":2:{s:4:"name";s:6:"007fly";s:4:"blog";s:33:"https://www.cnblogs.com/mysky007/";}
“O”即Object对象,“4”为对象名的长度,“Test”即对象名,“2”为对象的属性个数;进入大括号为属性的内容,“s”即string字符串类型,“4”即该属性名的长度,“name”即该属性名,接着“;”分号间隔键值或不同属性
经过反序列化后的结果如下:
__PHP_Incomplete_Class_Name => Test
name => 007fly
blog => https://www.cnblogs.com/mysky007/
反序列化漏洞
基本概念
PHP在进行反序列化操作时,若存在相应的魔法函数、unserialize()函数的参数可控且可以传递到魔法函数中执行相应的敏感操作,则会造成PHP反序列化漏洞的风险。
利用前提
unserialize()函数的参数可控;
代码中存在一个构造函数、析构函数、__wakeup()函数中有向php文件中写数据的操作的类或执行PHP代码或命令执行的类;
所写的内容需要有对象中的成员变量的值。
PHP魔法函数
下面列下可能经常碰到的魔法函数,其余的查查资料也知道了。
__construct():构造函数,当一个对象创建时被调用。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
__destruct():析构函数,当一个对象销毁时被调用。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
__toString():当一个对象被当作一个字符串使用。此方法必须返回一个字符串,否则将发出一条E_RECOVERABLE_ERROR级别的致命错误。
__sleep():常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个E_NOTICE级别的错误。
__wakeup():经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。unserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用__wakeup(),预先准备对象需要的资源。
一个简单的测试test3.php
1 <?php 2 class Vuln{ 3 public $name; 4 public $blog; 5 function __construct(){ 6 echo "[*]调用__construct()<br>"; 7 } 8 function __destruct(){ 9 echo "[*]调用__destruct()<br>"; 10 } 11 function __wakeup(){ 12 echo "[*]调用__wakeup()<br>"; 13 } 14 function __sleep(){ 15 echo "[*]调用__sleep()<br>"; 16 return array('name', 'blog'); 17 } 18 function __toString(){ 19 echo "[*]调用__toString()<br>"; 20 return $this->name." : ".$this->blog."<br>"; 21 } 22 } 23 24 echo "开始初始化对象...<br>"; 25 $test = new Vuln(); 26 $test->name = "007fly"; 27 $test->blog = "https://www.cnblogs.com/mysky007/"; 28 echo "创建对象并给其属性赋值:<br>"; 29 foreach($test as $key => $value) { 30 echo $key." => ".$value."<br>"; 31 } 32 33 echo "开始序列化对象...<br>"; 34 $str = serialize($test); 35 echo "对象序列化后的字符串:".$str."<br>"; 36 echo "开始反序列化对象...<br>"; 37 $str2 = unserialize($str); 38 echo $str2; 39 ?>
反序列化漏洞Demo
test4.php
1 <?php 2 class demo{ 3 public $name; 4 function __destruct(){ 5 eval($this->name); 6 //eval()函数是可以将函数里的字符串变成PHP代码来执行,且该 7 //字符串必须是合法的PHP代码,且必须以";"结尾 8 } 9 } 10 $a = new demo(); 11 $b = serialize($a); 12 echo $b."</br>"; 13 $str = $_GET['id']; 14 unserialize($str); 15 ?>
然后根据利用前提条件,构造序列化的字符串payload访问即可:
命令执行,只需将eval()换为system()等即可:
1 <?php 2 class Vuln{ 3 public $name; 4 function __destruct(){ 5 system($this->name); 6 } 7 } 8 9 $str = $_GET['007fly']; 10 unserialize($str); 11 12 ?>
反序列化防御手段:
1、要严格控制unserialize()函数的参数,坚持用户所输入的信息都是不可靠的原则;
2、要对于反序列化后的变量内容进行检查,以确定内容没有被污染