目录

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

# 流程

  1. apt换源
  2. 安装docker、docker compose
  3. 配置 docker 镜像加速
  4. 搭建配置 GZCTF 平台
  5. 拉取DVWA,sqlilabs,pikachu,rce-labs等靶场镜像
  6. 设置开机横幅内容,显示靶机ip
  7. 清除命令历史记录

以下流程在root用户家目录进行,实际流程可能没有按照上面顺序进行,但最终效果一致。

# 换源

中科大镜像源

sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
1

参考:

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
1
2

验证安装成功

root@debian:~# docker -v
Docker version 28.5.2, build ecc6942
root@debian:~# docker compose version
Docker Compose version v2.40.3
1
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
1
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)
1
2
3
4

入口虚拟机ip的问题,我该怎么解决..?有了,准备一份模板配置文件,ip使用一个不重复的变量名___GZCTF_PUBLIC_IP___占位,每次开机检查ip,把实际ip替换到模板配置文件中,生成一份新的配置文件使用

写入appsettings.json.template

sudo vim appsettings.json
1
{
  "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"]
  }
}
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

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"
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

# 开机横幅

vim /root/issue.template
1

可以在这个站点生成 艺术字ascll https://www.asciiart.eu/text-to-ascii-art (opens new window)

自定义横幅内容

Web Virtual Lab
VM IP:  __GZCTF_PUBLIC_IP__    

username: admin
password: Admin123..
1
2
3
4
5

# 注册开机自启服务

注册定时任务,每次开机时,替换靶机ip地址,确保入口正确

vim /etc/systemd/system/ctf.service
1

内容

[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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

脚本添加执行权限

chmod +x /root/replace-ip.sh
1

脚本内容:

#!/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
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

重新加载 systemd 配置

systemctl daemon-reload
1

开机自启

systemctl enable ctf.service
1

立即执行,测试是否存在错误

systemctl start ctf.service
1

创建启动 GZCTF

docker compose up -d
1

# 拉取题目镜像

# 集成靶场

rce-labs 系列

docker pull the0n3/rce-labs:latest
1

https://github.com/NUDTTAN91/GZCTF-Challenges

DVWA 系列

docker pull vulnerables/web-dvwa
1

pikachu 系列

docker pull area39/pikachu:latest
1

sqlilabs 系列

docker pull c0ny1/sqli-labs:0.1
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
1
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
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

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
1
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();
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

# 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);
  }
})();
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

# 接口批量启用题目

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();
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

# 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();
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
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();
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

# 收尾

清除命令历史记录

shred -u /root/.bash_history
shred -u /home/welcome/.bash_history
history -cw
1
2
3
最后一次更新于: 2025/11/10, 22:43:56