实战指南:利用CTFTraining题库与Docker-Compose在CTFd中一键部署Web靶场
1. 从零开始:CTFd与Web靶场部署基础
搞CTF的朋友都知道,Web题目是最让人又爱又恨的类型。爱的是它能模拟真实漏洞场景,恨的是部署起来总是一堆坑。去年我帮学校搭CTF比赛平台时,光是部署Web题目就折腾了整整两天。后来发现了CTFTraining这个宝藏题库和Docker-Compose的组合,部署效率直接提升10倍。
先说下基本配置要求。你需要:
- 已经搭建好的CTFd平台(建议1.3.0以上版本)
- Linux服务器(Ubuntu 18.04/20.04最稳定)
- Docker环境(版本19.03+)
- 基础命令行操作能力
为什么推荐这个方案?实测下来有三个明显优势:
- 题库标准化:CTFTraining收集了历年各大比赛的真题,环境配置都已经调试好
- 隔离性强:每个题目独立容器,互不干扰
- 快速回滚:题目出问题时,一条命令就能重置
我第一次用这个方案部署CISCN 2019的Web题,从下载到上线只用了7分钟。相比之前手动配环境动不动就报错,简直是降维打击。
2. 环境准备:Docker全家桶配置指南
2.1 Docker引擎安装
很多教程会推荐用curl -sSL https://get.docker.com | sh一键安装,但我强烈建议用apt手动安装。一键脚本经常会把测试版包装上,后面各种兼容性问题。这是我验证过最稳的安装方式:
# 卸载旧版本 sudo apt-get remove docker docker-engine docker.io containerd runc # 安装依赖 sudo apt-get update sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ gnupg \ lsb-release # 添加官方GPG密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 设置稳定版仓库 echo \ "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装Docker引擎 sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io装完后一定要测试:
sudo docker run hello-world看到"Hello from Docker!"才算成功。如果卡在pull镜像,记得配置国内镜像源:
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://你的镜像地址.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker2.2 Docker-Compose安装
虽然现在Docker自带compose插件,但CTFTraining的题目都是按老版本写的,建议还是装独立的docker-compose:
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose验证版本:
docker-compose --version # 应该显示 1.29.2这里有个坑:如果服务器在国内,GitHub可能连不上。可以先用本地机器下载,再scp传上去:
scp docker-compose-Linux-x86_64 user@your_server:/usr/local/bin/docker-compose3. 题库部署实战:以CISCN2019真题为例
3.1 题目获取与目录规划
CTFTraining的题目都在GitHub上,但直接clone整个仓库没必要。我习惯按比赛年份分类存放:
mkdir -p ~/CTF/{CISCN,CTFHub,RealWorld}/web cd ~/CTF/CISCN/web git clone https://github.com/CTFTraining/ciscn_2019_web_northern_china_day1_web1.git目录结构建议这样组织:
~/CTF/ ├── CISCN/ │ ├── web/ │ │ ├── ciscn_2019_web1/ │ │ │ ├── docker-compose.yml │ │ │ ├── www/ │ │ │ └── ... ├── CTFHub/ └── RealWorld/3.2 配置文件魔改技巧
用vim打开docker-compose.yml后,重点修改三个地方:
version: '2' services: web: build: . ports: - "0.0.0.0:12345:80" # 修改端口映射 environment: - FLAG=flag{test_flag_123} # 设置自定义flag restart: always避坑指南:
- 端口建议选30000-40000范围,避免冲突
- flag格式要符合CTFd校验规则(默认要求flag{}包裹)
- 如果题目需要MySQL等组件,要检查links配置
我遇到过最奇葩的问题是题目需要特定PHP版本,但dockerfile里没指定。这时候要自己改Dockerfile:
FROM php:5.6-apache # 明确指定版本 RUN docker-php-ext-install mysql COPY www/ /var/www/html/3.3 容器启动与调试
启动命令看似简单,但有些隐藏参数很实用:
docker-compose up -d --build # 强制重新构建镜像 docker-compose logs -f web # 实时查看日志常见问题排查:
- 端口占用:
netstat -tulnp | grep 端口号 - 容器启动失败:
docker inspect 容器ID | grep -A 10 "State" - 题目无法访问:检查iptables规则
sudo iptables -L -n
有一次部署某SSRF题目时,容器一直重启。后来发现是题目代码里有exit(0)导致服务退出。解决方法是在docker-compose.yml里加:
stdin_open: true tty: true4. CTFd集成:题目上线全流程
4.1 前端配置要点
登录CTFd后台,在"Challenges"页面点击"New Challenge",关键配置项:
- Name:题目名称(建议包含年份和比赛名称)
- Category:Web
- Value:动态分值建议初始500
- Description:可以用HTML写解题提示
最容易被忽略的是Connection Info字段,格式必须是:
http://你的服务器IP:端口号4.2 Flag设置技巧
在"Flags"标签页添加flag时,有几个实用功能:
- 动态flag:可以用
{{}}模板变量 - 正则匹配:比如设置
flag{.*}匹配任意flag - 区分大小写:根据题目需求勾选
实测发现一个坑:如果flag里含特殊字符如$,需要加转义符\。
4.3 题目测试流程
上线前一定要走完整测试流程:
- 用参赛者账号登录尝试解题
- 检查flag提交是否正常
- 测试多次错误提交后的提示
- 验证容器在高并发下的稳定性
我曾经遇到过选手提交flag后CTFd没反应,最后发现是Nginx配置了body大小限制。解决方法是在CTFd的Nginx配置里加:
client_max_body_size 10M;5. 高阶技巧与故障处理
5.1 批量部署方案
当需要部署20+题目时,手动操作会累死。可以用这个脚本自动处理:
#!/bin/bash for dir in ~/CTF/*/web/*/; do cd "$dir" || exit docker-compose up -d port=$(grep -oP 'ports:.*?\K\d+' docker-compose.yml) echo "${dir} 已启动,端口 ${port}" done更专业的做法是用Ansible,这里给出playbook示例:
- hosts: ctf_servers tasks: - name: 部署Web题目 docker_compose: project_src: "/home/ctf/{{ item }}" state: present with_items: - "CISCN/web/ciscn_2019_web1" - "CTFHub/web/sql_injection"5.2 资源监控与优化
用这个命令监控容器资源占用:
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"遇到性能瓶颈时可以:
- 限制CPU:
docker-compose.yml里加cpus: 0.5 - 限制内存:
mem_limit: 512m - 重启策略:
restart: unless-stopped
5.3 常见报错解决方案
问题1:端口冲突
ERROR: for web Cannot start service web: driver failed programming external connectivity on endpoint web (): Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use解决:
sudo ss -tulnp | grep 80 # 找出占用进程 sudo systemctl stop nginx # 停止冲突服务问题2:权限拒绝
Got permission denied while trying to connect to the Docker daemon socket解决:
sudo usermod -aG docker $USER newgrp docker问题3:镜像拉取失败
ERROR: pull access denied for web, repository does not exist or may require 'docker login'解决:检查docker-compose.yml里的image名称是否拼写错误
6. 安全加固与维护
6.1 容器隔离配置
默认配置下容器有安全隐患,建议在docker-compose.yml里添加:
security_opt: - no-new-privileges:true cap_drop: - ALL read_only: true tmpfs: - /tmp:rw,size=10M6.2 日志收集方案
用ELK收集题目日志的配置示例:
logging: driver: "syslog" options: syslog-address: "tcp://你的ELK服务器:514" tag: "{{.Name}}"6.3 定期维护脚本
这个脚本可以清理旧容器和悬空镜像:
#!/bin/bash # 停止所有运行中的题目 docker stop $(docker ps -aq --filter "label=com.docker.compose.project=ctf") # 清理一周前的日志 find /var/lib/docker/containers -name "*.log" -mtime +7 -delete # 移除悬空镜像 docker image prune -f # 重启所有题目 docker start $(docker ps -aq --filter "label=com.docker.compose.project=ctf")建议每天凌晨3点运行:
0 3 * * * /path/to/cleanup.sh >> /var/log/ctf_clean.log 2>&17. 实战案例:部署一个完整比赛
去年给某高校搭建CTF比赛时,我用这套方案部署了30道Web题。具体步骤:
选题规划:
- 简单题:5道(SQL注入、XSS等基础漏洞)
- 中等题:15道(SSRF、反序列化等)
- 难题:10道(内存破坏、沙箱逃逸等)
服务器配置:
- 8核16G云服务器 × 3台
- 每台部署10道题
- 用Nginx做负载均衡
部署时间表:
- 环境准备:1小时
- 批量部署题目:2小时
- 压力测试:1小时
- 监控配置:0.5小时
比赛期间遇到最棘手的问题是某道题的容器不断重启。后来发现是选手的爆破请求触发了PHP-FPM的max_children限制。临时解决方案是:
docker exec -it 容器ID bash -c "echo 'pm.max_children = 100' >> /etc/php/7.4/fpm/php-fpm.conf && service php7.4-fpm restart"这个方案最大的优势是弹性扩展。当监测到某道题访问量激增时,可以快速克隆到新服务器:
# 在新服务器上 rsync -avzP root@原服务器:/home/CTF/CISCN/web/ciscn_2019_web1 . docker-compose up -d --scale web=3 # 启动3个实例