CTF Web靶场搭建
ctf靶场搭建
介绍:使用Debian12系统,通过docker搭建GZCTF平台,并部署多个CTF靶场,导出虚拟机供新人在自己的主机上导入使用,环境隔离,方便快捷,移出新人学习CTF web方向时的环境搭建问题。
比较遗憾的是没有截图记录


# 配置
系统:debian 12
用户:root,welcome
root letme1n!!!
welcome welcome2the0n3.top
GZCTF管理员 用户名:admin 密码:Admin123..
GZCTF工作目录: /root/ appsettings.json模板文件路径: /root/appsettings.json.template compose.yml文件路径: /root/compose.yml ip替换脚本路径: /root/replace_ip.sh IP替换注册为开机启动任务:/etc/systemd/system/ctf.service
# 流程
- apt换源
- 安装docker、docker compose
- 配置 docker 镜像加速
- 搭建配置 GZCTF 平台
- 拉取DVWA,sqlilabs,pikachu,rce-labs等靶场镜像
- 设置开机横幅内容,显示靶机ip
- 清除命令历史记录
以下流程在root用户家目录进行,实际流程可能没有按照上面顺序进行,但最终效果一致。
# 换源
中科大镜像源
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
参考:
https://mirrors.ustc.edu.cn/help/debian.html
# 安装docker、docker compose
使用docker官方脚本安装docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
2
验证安装成功
root@debian:~# docker -v
Docker version 28.5.2, build ecc6942
root@debian:~# docker compose version
Docker Compose version v2.40.3
2
3
4
# 配置 docker 镜像加速
mkdir -p /etc/docker
cat > /etc/docker/daemon.json <<-EOF
{
"registry-mirrors": ["https://docker.the0n3.top/"]
}
EOF
systemctl daemon-reload
systemctl restart docker
2
3
4
5
6
7
8
参考:
https://www.runoob.com/docker/debian-docker-install.html
# 搭建 GZCTF 平台
准备了以下参数
GZCTF_ADMIN_PASSWORD: Admin123..(GZCTF管理员密码)
POSTGRES_PASSWORD: gzctf123.. (数据库密码)
XOR_KEY: thisXorKey (加密比赛的随机字符串)
PUBLIC_ENTRY: 192.168.237.140 (虚拟机ip)
2
3
4
入口虚拟机ip的问题,我该怎么解决..?有了,准备一份模板配置文件,ip使用一个不重复的变量名
___GZCTF_PUBLIC_IP___占位,每次开机检查ip,把实际ip替换到模板配置文件中,生成一份新的配置文件使用
写入appsettings.json.template
sudo vim appsettings.json
{
"AllowedHosts": "*",
"ConnectionStrings": {
"Database": "Host=db:5432;Database=gzctf;Username=postgres;Password=gzctf123.."
},
"EmailConfig": {
"SenderAddress": "",
"SenderName": "",
"UserName": "",
"Password": "",
"Smtp": {
"Host": "localhost",
"Port": 587
}
},
"XorKey": "thisXorKey",
"ContainerProvider": {
"Type": "Docker",
"PortMappingType": "Default",
"EnableTrafficCapture": false,
"PublicEntry": "___GZCTF_PUBLIC_IP___",
"DockerConfig": {
"SwarmMode": false,
"Uri": "unix:///var/run/docker.sock"
}
},
"RequestLogging": false,
"DisableRateLimit": true,
"RegistryConfig": {
"UserName": "",
"Password": "",
"ServerAddress": ""
},
"CaptchaConfig": {
"Provider": "None",
"SiteKey": "<Your SITE_KEY>",
"SecretKey": "<Your SECRET_KEY>",
"GoogleRecaptcha": {
"VerifyAPIAddress": "https://www.recaptcha.net/recaptcha/api/siteverify",
"RecaptchaThreshold": "0.5"
}
},
"ForwardedOptions": {
"ForwardedHeaders": 7,
"ForwardLimit": 1,
"TrustedNetworks": ["192.168.12.0/8"]
}
}
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
compose.yml
services:
gzctf:
image: registry.cn-shanghai.aliyuncs.com/gztime/gzctf:develop
restart: always
environment:
- "GZCTF_ADMIN_PASSWORD=Admin123.." # GZCTF admin password
# choose your backend language `en_US` / `zh_CN` / `ja_JP`
- "LC_ALL=zh_CN.UTF-8"
ports:
- "80:8080"
volumes:
- "./data/files:/app/files"
- "./appsettings.json:/app/appsettings.json:ro"
# - "./kube-config.yaml:/app/kube-config.yaml:ro" # this is required for k8s deployment
- "/var/run/docker.sock:/var/run/docker.sock" # this is required for docker deployment
depends_on:
- db
db:
image: postgres:alpine
restart: always
environment:
- "POSTGRES_PASSWORD=gzctf123.." # database password
volumes:
- "./data/db:/var/lib/postgresql/data"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 开机横幅
vim /root/issue.template
可以在这个站点生成 艺术字ascll https://www.asciiart.eu/text-to-ascii-art (opens new window)
自定义横幅内容
Web Virtual Lab
VM IP: __GZCTF_PUBLIC_IP__
username: admin
password: Admin123..
2
3
4
5
# 注册开机自启服务
注册定时任务,每次开机时,替换靶机ip地址,确保入口正确
vim /etc/systemd/system/ctf.service
内容
[Unit]
Description=GZCTF Platform Auto-Configuration and Start
# 确保在网络连接就绪后才运行
After=network-online.target
# 确保在 Docker 服务启动后才运行,因为脚本需要执行 docker-compose 命令
After=docker.service
Requires=docker.service
[Service]
# Type=oneshot 表示任务执行一次就结束,不会常驻内存
Type=oneshot
# 脚本的绝对路径
ExecStart=/root/replace-ip.sh
# 确保脚本以 root 权限运行,因为需要修改 /etc/docker 和执行 docker 命令
User=root
[Install]
# 服务将在多用户模式下启动时被激活
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
脚本添加执行权限
chmod +x /root/replace-ip.sh
脚本内容:
#!/bin/bash
# 获取主机 IP(排除 127.0.0.1 和 docker 网络)
CURRENT_IP=$(ip -4 addr show | grep -v "127.0.0.1" | grep -v "docker" | grep -oP 'inet \K[\d.]+' | head -n 1)
if [ -z "$CURRENT_IP" ]; then
echo "Could not detect a valid external/host IP address."
exit 1
else
echo "Host IP Address: $CURRENT_IP"
fi
# 定义居中函数
center() {
termwidth=$(tput cols 2>/dev/null || echo 80)
while IFS= read -r line; do
printf "%*s\n" $(( (${#line} + termwidth) / 2 )) "$line"
done
}
# 生成最终横幅(/etc/issue),居中显示
sed "s/___GZCTF_PUBLIC_IP___/$CURRENT_IP/g" /root/issue.template | center > /etc/issue
chmod 644 /etc/issue
# 重启 getty 服务以应用新的横幅
setfont /usr/share/consolefonts/Lat2-TerminusBold28x14.psf.gz
systemctl restart getty@tty1.service
# 更新 appsettings.json
cp /root/appsettings.json.template /root/appsettings.json
sed -i "s/___GZCTF_PUBLIC_IP___/$CURRENT_IP/g" /root/appsettings.json
# 停止/清理应用
cd /root/
docker compose down >> /root/docker-compose.log 2>&1
# 重新启动
docker compose up -d >> /root/docker-compose.log 2>&1
# 日志记录
echo "replace-ip.sh run successfully $(date)" >> /root/ctf-startup.log
# 日志清理
find /root/ -type f -name "*.log-*" -mtime +7 -delete
echo "--- 日志清理完成 ---" >> /root/ctf-startup.log
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
重新加载 systemd 配置
systemctl daemon-reload
开机自启
systemctl enable ctf.service
立即执行,测试是否存在错误
systemctl start ctf.service
创建启动 GZCTF
docker compose up -d
# 拉取题目镜像
# 集成靶场
rce-labs 系列
docker pull the0n3/rce-labs:latest
https://github.com/NUDTTAN91/GZCTF-Challenges
DVWA 系列
docker pull vulnerables/web-dvwa
pikachu 系列
docker pull area39/pikachu:latest
sqlilabs 系列
docker pull c0ny1/sqli-labs:0.1
# 单关卡靶场
md5bypass 系列
docker pull nudttan91/md5bypass:pass01_gzctf
docker pull nudttan91/md5bypass:pass02_gzctf
docker pull nudttan91/md5bypass:pass03_gzctf
docker pull nudttan91/md5bypass:pass04_gzctf
docker pull nudttan91/md5bypass:pass05_gzctf
docker pull nudttan91/md5bypass:pass06_gzctf
docker pull nudttan91/md5bypass:pass07_gzctf
docker pull nudttan91/md5bypass:pass08_gzctf
2
3
4
5
6
7
8
sqli-labs 系列
docker pull nudttan91/sqli_labs_gzctf:less01
docker pull nudttan91/sqli_labs_gzctf:less02
docker pull nudttan91/sqli_labs_gzctf:less03
docker pull nudttan91/sqli_labs_gzctf:less04
docker pull nudttan91/sqli_labs_gzctf:less05
docker pull nudttan91/sqli_labs_gzctf:less06
docker pull nudttan91/sqli_labs_gzctf:less07
docker pull nudttan91/sqli_labs_gzctf:less08
docker pull nudttan91/sqli_labs_gzctf:less09
docker pull nudttan91/sqli_labs_gzctf:less10
docker pull nudttan91/sqli_labs_gzctf:less11
docker pull nudttan91/sqli_labs_gzctf:less12
docker pull nudttan91/sqli_labs_gzctf:less13
docker pull nudttan91/sqli_labs_gzctf:less14
docker pull nudttan91/sqli_labs_gzctf:less15
docker pull nudttan91/sqli_labs_gzctf:less16
docker pull nudttan91/sqli_labs_gzctf:less17
docker pull nudttan91/sqli_labs_gzctf:less18
docker pull nudttan91/sqli_labs_gzctf:less19
docker pull nudttan91/sqli_labs_gzctf:less20
docker pull nudttan91/sqli_labs_gzctf:less21
docker pull nudttan91/sqli_labs_gzctf:less22
docker pull nudttan91/sqli_labs_gzctf:less23
docker pull nudttan91/sqli_labs_gzctf:less24
docker pull nudttan91/sqli_labs_gzctf:less25
docker pull nudttan91/sqli_labs_gzctf:less25a
docker pull nudttan91/sqli_labs_gzctf:less26
docker pull nudttan91/sqli_labs_gzctf:less26a
docker pull nudttan91/sqli_labs_gzctf:less27
docker pull nudttan91/sqli_labs_gzctf:less27a
docker pull nudttan91/sqli_labs_gzctf:less28
docker pull nudttan91/sqli_labs_gzctf:less28a
docker pull nudttan91/sqli_labs_gzctf:less29
docker pull nudttan91/sqli_labs_gzctf:less30
docker pull nudttan91/sqli_labs_gzctf:less31
docker pull nudttan91/sqli_labs_gzctf:less32
docker pull nudttan91/sqli_labs_gzctf:less33
docker pull nudttan91/sqli_labs_gzctf:less34
docker pull nudttan91/sqli_labs_gzctf:less35
docker pull nudttan91/sqli_labs_gzctf:less36
docker pull nudttan91/sqli_labs_gzctf:less37
docker pull nudttan91/sqli_labs_gzctf:less38
docker pull nudttan91/sqli_labs_gzctf:less39
docker pull nudttan91/sqli_labs_gzctf:less40
docker pull nudttan91/sqli_labs_gzctf:less41
docker pull nudttan91/sqli_labs_gzctf:less42
docker pull nudttan91/sqli_labs_gzctf:less43
docker pull nudttan91/sqli_labs_gzctf:less44
docker pull nudttan91/sqli_labs_gzctf:less45
docker pull nudttan91/sqli_labs_gzctf:less46
docker pull nudttan91/sqli_labs_gzctf:less47
docker pull nudttan91/sqli_labs_gzctf:less48
docker pull nudttan91/sqli_labs_gzctf:less49
docker pull nudttan91/sqli_labs_gzctf:less50
docker pull nudttan91/sqli_labs_gzctf:less51
docker pull nudttan91/sqli_labs_gzctf:less52
docker pull nudttan91/sqli_labs_gzctf:less53
docker pull nudttan91/sqli_labs_gzctf:less54
docker pull nudttan91/sqli_labs_gzctf:less55
docker pull nudttan91/sqli_labs_gzctf:less56
docker pull nudttan91/sqli_labs_gzctf:less57
docker pull nudttan91/sqli_labs_gzctf:less58
docker pull nudttan91/sqli_labs_gzctf:less59
docker pull nudttan91/sqli_labs_gzctf:less60
docker pull nudttan91/sqli_labs_gzctf:less61
docker pull nudttan91/sqli_labs_gzctf:less62
docker pull nudttan91/sqli_labs_gzctf:less63
docker pull nudttan91/sqli_labs_gzctf:less64
docker pull nudttan91/sqli_labs_gzctf:less65
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
upload-labs 系列
docker pull nudttan91/upload_labs_gzctf:pass01
docker pull nudttan91/upload_labs_gzctf:pass02
docker pull nudttan91/upload_labs_gzctf:pass03
docker pull nudttan91/upload_labs_gzctf:pass04
docker pull nudttan91/upload_labs_gzctf:pass05
docker pull nudttan91/upload_labs_gzctf:pass06
docker pull nudttan91/upload_labs_gzctf:pass07
docker pull nudttan91/upload_labs_gzctf:pass08
docker pull nudttan91/upload_labs_gzctf:pass09
docker pull nudttan91/upload_labs_gzctf:pass10
docker pull nudttan91/upload_labs_gzctf:pass11
docker pull nudttan91/upload_labs_gzctf:pass12
docker pull nudttan91/upload_labs_gzctf:pass13
docker pull nudttan91/upload_labs_gzctf:pass14
docker pull nudttan91/upload_labs_gzctf:pass15
docker pull nudttan91/upload_labs_gzctf:pass16
docker pull nudttan91/upload_labs_gzctf:pass17
docker pull nudttan91/upload_labs_gzctf:pass18
docker pull nudttan91/upload_labs_gzctf:pass19
docker pull nudttan91/upload_labs_gzctf:pass20
docker pull nudttan91/upload_labs_gzctf:pass21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# sqlilabs 批量上题
sqlilab单兵靶场
浏览器控制台批量上题,注意一定要修改ip
// 注意 games/{id}/ ,这里的id是比赛的次序,即使删除之前的题目,id也会递增
const baseUrl = "http://192.168.219.143/api/edit/games/2/challenges";
// 所有题目名称,包括带 a 的关卡
const challengeNames = [
"less01","less02","less03","less04","less05","less06","less07","less08","less09","less10",
"less11","less12","less13","less14","less15","less16","less17","less18","less19","less20",
"less21","less22","less23","less24","less25","less25a","less26","less26a","less27","less27a",
"less28","less28a","less29","less30","less31","less32","less33","less34","less35","less36",
"less37","less38","less39","less40","less41","less42","less43","less44","less45","less46",
"less47","less48","less49","less50","less51","less52","less53","less54","less55","less56",
"less57","less58","less59","less60","less61","less62","less63","less64","less65"
];
// 延时函数
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
// 1️⃣ 创建题目
async function createChallenge(title) {
const res = await fetch(baseUrl, {
method: "POST",
headers: { "accept": "application/json, text/plain, */*", "content-type": "application/json" },
credentials: "include",
body: JSON.stringify({ title, category: "Web", type: "DynamicContainer" })
});
const data = await res.json();
console.log(`✅ 已创建题目 "${title}",ID: ${data.id}`);
return data.id;
}
// 2️⃣ 配置题目信息
async function updateChallenge(id, title) {
const body = {
id,
title,
content: "",
category: "Web",
type: "DynamicContainer",
hints: [],
flagTemplate: null,
acceptedCount: 0,
fileName: "attachment",
attachment: null,
testContainer: null,
flags: [],
containerImage: `nudttan91/sqli_labs_gzctf:${title}`,
memoryLimit: 64,
cpuCount: 1,
storageLimit: 256,
containerExposePort: 80,
enableTrafficCapture: false,
disableBloodBonus: false,
submissionLimit: 0,
originalScore: 1000,
minScoreRate: 0.25,
difficulty: 5
};
await fetch(`${baseUrl}/${id}`, {
method: "PUT",
headers: { "accept": "application/json, text/plain, */*", "content-type": "application/json" },
credentials: "include",
body: JSON.stringify(body)
});
console.log(`⚙ 已更新题目 "${title}" 的详细信息`);
}
// 3️⃣ 配置 flag 模板
async function setFlagTemplate(id, flag = "flag{[GUID]}") {
await fetch(`${baseUrl}/${id}`, {
method: "PUT",
headers: { "accept": "application/json, text/plain, */*", "content-type": "application/json" },
credentials: "include",
body: JSON.stringify({ flagTemplate: flag })
});
console.log(`🏷 已为题目 ID ${id} 设置 flag 模板`);
}
// 4️⃣ 批量处理
async function batchSetup() {
for (const name of challengeNames) {
try {
const id = await createChallenge(name);
await sleep(200); // 等待 200ms
await updateChallenge(id, name);
await sleep(200);
await setFlagTemplate(id);
await sleep(200);
} catch (err) {
console.error(`❌ 处理题目 "${name}" 时发生错误:`, err);
}
}
console.log("🎉 所有题目处理完成!");
}
// 执行
batchSetup();
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
# sqlilabs 批量配置flag模板
// 题目 ID 数组,这里示例用 1~65
const challengeIds = Array.from({ length: 65 }, (_, i) => i + 1);
const setFlagTemplate = async (id) => {
const url = `http://192.168.219.143/api/edit/games/2/challenges/${id}`;
const body = JSON.stringify({ flagTemplate: "flag{[GUID]}" });
try {
const res = await fetch(url, {
method: "PUT",
headers: {
"accept": "application/json, text/plain, */*",
"content-type": "application/json",
},
body,
credentials: "include",
});
if (res.ok) {
console.log(`Challenge ${id} flagTemplate updated successfully`);
} else {
console.error(`Challenge ${id} update failed: ${res.status}`);
}
} catch (err) {
console.error(`Challenge ${id} update error:`, err);
}
};
// 顺序执行,也可以用 Promise.all 并发执行
(async () => {
for (const id of challengeIds) {
await setFlagTemplate(id);
}
})();
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
# 接口批量启用题目
const gameId = 2; // 您的目标游戏 ID
const baseUrl = `http://192.168.219.143/api/edit/games`;
async function getAllChallenges() {
// 使用您已验证成功的逻辑获取所有题目
const res = await fetch(`${baseUrl}/${gameId}/challenges`, {
method: 'GET',
headers: { 'accept': 'application/json, text/plain, */*', },
credentials: 'include'
});
if (!res.ok) {
console.error('❌ 获取题目列表失败:', res.status, res.statusText);
return [];
}
const challenges = await res.json();
console.log(`✅ 共获取到 ${challenges.length} 个题目,准备批量启用...`);
return challenges;
}
async function enableChallenge(challengeId) {
// 启用单个题目的 API 地址是:/api/edit/challenges/{challengeId},它独立于 GameId。
// GZCTF 的 PUT /api/edit/games/{gameId}/challenges/{challengeId} 是更新题目属性的端点。
// 为了启用,我们发送 isEnabled: true
const enableUrl = `${baseUrl}/${gameId}/challenges/${challengeId}`;
const res = await fetch(enableUrl, {
method: "PUT",
headers: {
"accept": "application/json, text/plain, */*",
"content-type": "application/json"
},
// 启用题目:isEnabled: true
body: JSON.stringify({ isEnabled: true }),
credentials: "include"
});
if (res.ok) {
console.log(`✅ 题目 ID: ${challengeId} 已启用。`);
} else {
console.error(`❌ 题目 ID: ${challengeId} 启用失败。状态码: ${res.status}`);
}
}
async function enableAllChallengesInGame() {
const challenges = await getAllChallenges();
if (challenges.length === 0) {
console.log("🚫 无题目可启用。");
return;
}
for (const ch of challenges) {
// 强制设置 isEnabled: true,并更新题目
await enableChallenge(ch.id);
}
console.log("\n🎉 Game ID 2 下的所有题目已处理完成!");
}
// 执行启用函数
enableAllChallengesInGame();
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
# upload-labs批量上题
// ==========================================================
// 🚀 Upload Labs 靶场批量导入脚本 (Game ID 4)
// ==========================================================
// 1️⃣ 修改基础 URL 和 Game ID
// 注意:baseUrl 现在指向 http://192.168.219.143/api/edit/games/4/challenges
const baseUrl = "http://192.168.219.143/api/edit/games/4/challenges";
// 2️⃣ 修改所有题目名称 (Pass 01 - Pass 21)
const challengeNames = [
"pass01", "pass02", "pass03", "pass04", "pass05", "pass06", "pass07", "pass08", "pass09", "pass10",
"pass11", "pass12", "pass13", "pass14", "pass15", "pass16", "pass17", "pass18", "pass19", "pass20",
"pass21"
];
// 延时函数
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
// 1️⃣ 创建题目
async function createChallenge(title) {
const res = await fetch(baseUrl, {
method: "POST",
headers: { "accept": "application/json, text/plain, */*", "content-type": "application/json" },
credentials: "include",
body: JSON.stringify({ title, category: "Web", type: "DynamicContainer" })
});
// 增加容错:检查创建 API 状态
if (!res.ok) {
console.error(`❌ 创建题目 "${title}" 失败: ${res.status} ${res.statusText}`);
throw new Error("Creation failed");
}
const data = await res.json();
console.log(`✅ 已创建题目 "${title}",ID: ${data.id}`);
return data.id;
}
// 2️⃣ 配置题目信息
async function updateChallenge(id, title) {
const body = {
id,
title,
content: "",
category: "Web",
type: "DynamicContainer",
hints: [],
flagTemplate: null,
acceptedCount: 0,
fileName: "attachment",
attachment: null,
testContainer: null,
flags: [],
// 3️⃣ 修改镜像地址:适配 nudttan91/upload_labs_gzctf
containerImage: `nudttan91/upload_labs_gzctf:${title}`,
memoryLimit: 64,
cpuCount: 1,
storageLimit: 256,
containerExposePort: 80,
enableTrafficCapture: false,
disableBloodBonus: false,
submissionLimit: 0,
originalScore: 1000,
minScoreRate: 0.25,
difficulty: 5
};
const res = await fetch(`${baseUrl}/${id}`, {
method: "PUT",
headers: { "accept": "application/json, text/plain, */*", "content-type": "application/json" },
credentials: "include",
body: JSON.stringify(body)
});
if (!res.ok) {
console.error(`❌ 更新题目 "${title}" (ID: ${id}) 失败: ${res.status} ${res.statusText}`);
throw new Error("Update failed");
}
console.log(`⚙ 已更新题目 "${title}" 的详细信息`);
}
// 3️⃣ 配置 flag 模板
async function setFlagTemplate(id, flag = "flag{[GUID]}") {
const res = await fetch(`${baseUrl}/${id}`, {
method: "PUT",
headers: { "accept": "application/json, text/plain, */*", "content-type": "application/json" },
credentials: "include",
body: JSON.stringify({ flagTemplate: flag })
});
if (!res.ok) {
console.error(`❌ 设置 Flag 模板 (ID: ${id}) 失败: ${res.status} ${res.statusText}`);
throw new Error("Flag template failed");
}
console.log(`🏷 已为题目 ID ${id} 设置 flag 模板`);
}
// 4️⃣ 批量处理
async function batchSetup() {
console.log("--- 🚀 Upload Labs 题目批量导入开始 (Game ID 4) ---");
for (const name of challengeNames) {
try {
const id = await createChallenge(name);
await sleep(200); // 等待 200ms
await updateChallenge(id, name);
await sleep(200);
await setFlagTemplate(id);
await sleep(200);
} catch (err) {
// 捕获到错误后,跳过该题目,继续下一个
console.error(`❌ 处理题目 "${name}" 时发生错误,跳过该题目。`);
}
}
console.log("\n🎉 所有 Upload Labs 题目处理完成!");
}
// 执行
batchSetup();
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# uploads-lab 批量启用
const gameId = 4; // 比赛的id
const baseUrl = `http://192.168.219.143/api/edit/games`;
async function getAllChallenges() {
// 使用您已验证成功的逻辑获取所有题目
const res = await fetch(`${baseUrl}/${gameId}/challenges`, {
method: 'GET',
headers: { 'accept': 'application/json, text/plain, */*', },
credentials: 'include'
});
if (!res.ok) {
console.error('❌ 获取题目列表失败:', res.status, res.statusText);
return [];
}
const challenges = await res.json();
console.log(`✅ 共获取到 ${challenges.length} 个题目,准备批量启用...`);
return challenges;
}
async function enableChallenge(challengeId) {
// 启用单个题目的 API 地址是:/api/edit/challenges/{challengeId},它独立于 GameId。
// GZCTF 的 PUT /api/edit/games/{gameId}/challenges/{challengeId} 是更新题目属性的端点。
// 为了启用,我们发送 isEnabled: true
const enableUrl = `${baseUrl}/${gameId}/challenges/${challengeId}`;
const res = await fetch(enableUrl, {
method: "PUT",
headers: {
"accept": "application/json, text/plain, */*",
"content-type": "application/json"
},
// 启用题目:isEnabled: true
body: JSON.stringify({ isEnabled: true }),
credentials: "include"
});
if (res.ok) {
console.log(`✅ 题目 ID: ${challengeId} 已启用。`);
} else {
console.error(`❌ 题目 ID: ${challengeId} 启用失败。状态码: ${res.status}`);
}
}
async function enableAllChallengesInGame() {
const challenges = await getAllChallenges();
if (challenges.length === 0) {
console.log("🚫 无题目可启用。");
return;
}
for (const ch of challenges) {
// 强制设置 isEnabled: true,并更新题目
await enableChallenge(ch.id);
}
console.log("\n🎉 Game ID "+gameId+" 下的所有题目已处理完成!");
}
// 执行启用函数
enableAllChallengesInGame();
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
# 收尾
清除命令历史记录
shred -u /root/.bash_history
shred -u /home/welcome/.bash_history
history -cw
2
3