"我的心田本空无一物,你来之后万物生长,你走之后,一片荒芜" --《隐入尘烟》


一、前言

最近有个朋友准备搭建一个CTF比赛靶场在学校搞一个CTF比赛,让我帮他参考参考。说实话,自己也没有整过,自己就先实验呗,搜集了大量的资料最终是搞成了,就差朋友买个服务器,和域名挂上了,这期间经历过了大量的报错,因此,想记录下来,为兄弟们指一条明道,避免出错。另外本篇文章大部分是依赖网上这位兄弟的文章,因此我把原文章链接挂出来,

感谢这位兄弟:

https://www.yuque.com/docs/share/364ef08c-9405-45fe-b269-e9236de57242?#pBjVN, 

自己这样写主要是整理出来好查询,在加上自己遇到的问题和解决方案,以及汉化,题目发放的疑惑,值得写一下。

二、环境准备

  • 主机:服务器版本的CentOs7
  • 链接:http://centos.nethub.com.hk/7.9.2009/isos/x86_64/CentOS-7-x86_64-Minimal-2009.iso
  • Docker版本:20.10.2
  • Docker-compose版本:1.25.0
  • IP地址:公网地址或虚拟机地址

三、环境搭建

1.系统环境搭建配置:如果是阿里云服务器,直接更新yum源:

 yum update

2.如果是虚拟机不能直接更新yum源的话就换阿里源

centos 7 换阿里源方法:

  • 先下载  wget :一定要确保有wget
 yum install wget
  • 阿里源:
 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all
yum makecache
yum update

注:一定要运行yum update 确保软件的更新和下载

3.换源之后把openssh-server 进行安装,主要是为了方便命令的输入,和文件的传输

  • 运行:
yum install openssh-server
  • 下载vim 编辑工具:
 yum install vim 
  • 配置/etc/sshd/sshd_config 不然xshell等工具不能连接,改过之后如图所示:
  • 用xshell连接就能复制命令,方便下面的操作:

4.安装系统环境所需服务,主要是python,数据库 运行完就ok了。

  • 运行:
 yum install -y git nginx mariadb mariadb-server Mysql-python python-pip gcc python-devel yum- utils device-mapper-persistent-data lvm2 epel-release

  • 安装docker之前换源
  • 命令:
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  • 选择合适的版本并安装

 


  • 安装17.12.1.ce版本,可能很慢,耐心等待。
  • 运行:
yum -y install docker-ce-17.12.1.ce

5.DaoCloud配置docker镜像源加速

  • 运行:
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io
  • 查看是否安装成功并启动
  •    运行:
docker --version

 

6.安装docker-compose

# 下载docker compose
curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.4/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# 添加可执行权限
chmod +x /usr/local/bin/docker-compose
# 将文件copy到 /usr/bin/目录下
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
# 查看版本
docker-compose --version

 

2.靶场环境

1.下载CTFd

  • 运行:
git clone https://github.com/glzjin/CTFd.git
  • 下载frp
  •       运行:
wget https://github.com/fatedier/frp/releases/download/v0.29.0/frp_0.29.0_linux_amd64.tar.gz
  • 下载完成后解压备用:
tar -zxvf frp_0.29.0_linux_amd64.tar.gz

2.下载ctfd-whale

  • 运行:
git clone https://github.com/glzjin/CTFd-Whale.git
  • 将下载的CTFd-Whale文件夹重命名为小写:
mv CTFd-Whale/ ctfd-whale

3.下载docker版本的frps

  • 运行:
git clone https://github.com/glzjin/Frp-Docker-For-CTFd-Whale
  • 将下载后的Frp-Docker-For-CTFd-Whale也重命名为小写:
  • 运行:
mv Frp-Docker-For-CTFd-Whale/ frp-docker-for-ctfd-whale
  • 下载之后截图:


   注:如果不能下载,或者下载缓慢的话,可以挂代理,也可以下载下来在传到服务器上 

3.CTFd环境配置

1.Docker集群设置 初始化docker集群

  • 运行:
docker swarm init

   如果出现这种情况Cannot connect to the Docker daemon at unix:

  • 运行:
systemctl start docker
  • 然后再运行初始化命令:
docker swarm init

2.将刚刚初始化的这个集群加入到节点当中

命令执行后,返回的就是节点ID了,暂时不用管这个节点ID

  • 运行:
docker node update --label-add='name=linux-1' $(docker node ls -q)

3.ctfd-whale放入CTFd的插件配置

  • 将ctfd-whale放入CTFd的插件目录中
  • 运行:
mv ctfd-whale/ CTFd/CTFd/plugins/
  • 进入目录并配置frps.ini文件
  • 运行:       
 cd frp-docker-for-ctfd-whale/frp
 vim frps.ini

  改成图片所示:一般token不用更改,端口根据自己喜好更改,一般采用默认

4.修改完成后返回目录启动

注意:是 frp-docker-for-ctfd-whale目录

  • 运行:
 cd ..
 docker-compose up -d

    耐心等待

  • 构建完成以后查看是否正在运行
 docker ps

5.将frpc文件配置到CTFd中

    首先进入CTFd目录中,新建frpc文件夹

    运行:  

 (1)cd CTFd/   (2) mkdir frpc
  • 进入frpc的目录(frp_0.29.0_linux_amd64)将里面的frpc,frpc.ini,frpc_full.ini,LICENSE这四个文件放在CTFd/frpc文件夹中
cd ../frp_0.29.0_linux_amd64
mv frpc.ini ../CTFd/frpc/
mv frpc_full.ini ../CTFd/frpc/
mv frpc ../CTFd/frpc/
mv LICENSE ../CTFd/frpc/
  • 进入刚刚新建的CTFd/fprc目录,配置frpc.ini文件,直接复制粘贴
[common]
token = randomme
server_addr = 172.1.0.4
server_port = 6490
pool_count = 200
tls_enable = true
admin_addr = 172.1.0.3
admin_port = 7400

注意:这里的token要和frp-docker-for-ctfd-whale/frp/frps.ini的token一样,前面没改,这里就是randomme

  • 进入CTFd目录,将下方内容复制到Dockerfile中,直接复制粘贴
FROM python:3.6-alpine
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories &&\
 apk update && \
 apk add python3 python3-dev linux-headers libffi-dev gcc make musl-dev py-pip mysql-client git openssl-dev g++
RUN adduser -D -u 1001 -s /bin/bash ctfd
WORKDIR /opt/CTFd
RUN mkdir -p /opt/CTFd /var/log/CTFd /var/uploads
RUN pip3 config set global.index-url https://pypi.doubanio.com/simple
RUN pip3 config set install.trusted-host pypi.doubanio.com
COPY requirements.txt .
RUN pip install -r requirements.txt -i https://pypi.doubanio.com/simple
COPY . /opt/CTFd
RUN for d in CTFd/plugins/*; do \
  if [ -f "$d/requirements.txt" ]; then \
   pip install -r $d/requirements.txt -i https://pypi.doubanio.com/simple; \
  fi; \
 done;
RUN chmod +x /opt/CTFd/docker-entrypoint.sh
RUN chown -R 1001:1001 /opt/CTFd
RUN chown -R 1001:1001 /var/log/CTFd /var/uploads
USER 1001
EXPOSE 8000
ENTRYPOINT ["/opt/CTFd/docker-entrypoint.sh"]
  • 在CTFd目录配置docker-compose.yml文件,直接复制粘贴
version: '2.2'
services:
 ctfd-nginx:
 image: nginx:1.17
 volumes:
  - ./nginx/http.conf:/etc/nginx/nginx.conf 
 user: root
 restart: always
 ports:  
  - "443:443"
 networks:
   default:
   internal:
 depends_on:
  - ctfd
 cpus: '1.00' 
 mem_limit: 150M  
 ctfd:
 build: .
 user: root
 restart: always
 ports:
  - "8000:8000"  
 environment:
  - UPLOAD_FOLDER=/var/uploads
  - DATABASE_URL=mysql+pymysql://root:ctfd@db/ctfd
  - REDIS_URL=redis://cache:6379
  - WORKERS=1
  - LOG_FOLDER=/var/log/CTFd
  - ACCESS_LOG=-
  - ERROR_LOG=-
  - REVERSE_PROXY=true
 volumes:
  - .data/CTFd/logs:/var/log/CTFd
  - .data/CTFd/uploads:/var/uploads
  - .:/opt/CTFd:ro
  - /var/run/docker.sock:/var/run/docker.sock  
 depends_on:
  - db
 networks:
   default:
   internal:
   frp:
     ipv4_address: 172.1.0.2
 cpus: '1.00'  
 mem_limit: 450M  
 db:
 image: mariadb:10.4
 restart: always
 environment:
  - MYSQL_ROOT_PASSWORD=ctfd
  - MYSQL_USER=ctfd
  - MYSQL_PASSWORD=ctfd
 volumes:
  - .data/mysql:/var/lib/mysql
 networks:
   internal:
 command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --wait_timeout=28800, --log-warnings=0]
 cpus: '1.00'  
 mem_limit: 750M  
 cache:
 image: redis:4
 restart: always
 volumes:
  - .data/redis:/data
 networks:
   internal:
 cpus: '1.00'  
 mem_limit: 450M  
 frpc:  
 image: glzjin/frp:latest  
 restart: always
 volumes:
  - ./frpc:/conf/  
 entrypoint:
   - /usr/local/bin/frpc
   - -c
   - /conf/frpc.ini
 networks:
   frp:
     ipv4_address: 172.1.0.3 
   frp-containers:
 cpus: '1.00'  
 mem_limit: 250M  
networks:
 default:
 internal:
   internal: true
 frp:
   driver: bridge
   ipam:
     config:
       - subnet: 172.1.0.0/16
 frp-containers:
   driver: overlay
   internal: true
   ipam:
     config:
       - subnet: 172.2.0.0/16
  • 配置requirements.txt 这里主要是修改gevent版本号,gevent原本是1.4.0的,这边经过多次尝试,不指定版本号才能拉取,直接复制粘贴
Flask==1.1.1
Werkzeug==0.16.0
Flask-SQLAlchemy==2.4.1
Flask-Caching==1.4.0
Flask-Migrate==2.5.2
Flask-Script==2.0.6
SQLAlchemy==1.3.11
SQLAlchemy-Utils==0.36.0
passlib==1.7.2
bcrypt==3.1.7
six==1.13.0
itsdangerous==1.1.0
requests>=2.20.0
PyMySQL==0.9.3
gunicorn==19.9.0
normality==2.0.0
dataset==1.1.2
mistune==0.8.4
netaddr==0.7.19
redis==3.3.11
datafreeze==0.1.0
python-dotenv==0.10.3
flask-restplus==0.13.0
pathlib2==2.3.5
flask-marshmallow==0.10.1
marshmallow-sqlalchemy==0.17.0
boto3==1.10.39
marshmallow==2.20.2
gevent
tzlocal==2.1
  • 配置nginx,在CTFd的目录下,新建一个nginx文件夹并进入
mkdir nginx
cd nginx
  • 在上面创建的nginx目录下, 创建一个文件http.conf,输入以下内容,直接复制粘贴
worker_processes 4;
events {
 worker_connections 1024;
}
http {
 # Configuration containing list of application servers
 upstream app_servers {
 server ctfd:8000;
 }
 server {
 listen 80;
 client_max_body_size 4G;
 # Handle Server Sent Events for Notifications
 location /events {
  proxy_pass http://app_servers;
  proxy_set_header Connection '';
  proxy_http_version 1.1;
  chunked_transfer_encoding off;
  proxy_buffering off;
  proxy_cache off;
  proxy_redirect off;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Host $server_name;
 }
 # Proxy connections to the application servers
 location / {
  proxy_pass http://app_servers;
  proxy_redirect off;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Host $server_name;
 }
 }
}

6.开始构建,在CTFd目录下开始构建,运行下面的代码,耐心等待  

docker-compose up -d

拉取完镜像之后

  • 拉取完成后配置docker网络,先看一下现在的容器状态
docker ps -a

看到ctfd_frpc_1这个容器的状态是退出状态。

  • 查看docker的网络
docker network ls

7.需要将ctfd_frpc_1,frp-docker-for-ctfd-whale_frps_1,ctfd_ctfd_1这三个容器加入到ctfd_frp网络中    

并且这三个容器的IP如下:

 ctfd_ctfd_1:172.1.0.2

 ctfd_frpc_1:172.1.0.3

 frp-docker-for-ctfd-whale_frps_1:172.1.0.4

  • 查看一下ctfd_frp网络
docker network inspect ctfd_frp

我们需要把其他两个加进去

  • 只有ctfd_ctfd_1这个容器是在ctfd_frp网络里的,此时我们指定IP将其他两个容器加入ctfd_frp网络里
docker network connect --ip 172.1.0.3 ctfd_frp ctfd_frpc_1
docker network connect --ip 172.1.0.4 ctfd_frp frp-docker-for-ctfd-whale_frps_1
  • 再次查看一下ctfd_frp网络
  • 重启一下这两个容器
 docker restart ctfd_frpc_1 frp-docker-for-ctfd-whale_frps_1
  • 再次查看一下ctfd_frp网络
docker network inspect ctfd_frp

发现已经可以看到,这个时候,ctfd_frpc_1,frp-docker-for-ctfd-whale_frps_1,ctfd_ctfd_1这三个容器已经加入到ctfd_frp网络中了

  • 再查看一下ctfd_frpc_1容器如果没有一直重启就算配好了

  • 直接访问http://你的ip:8000

至此,靶场已经搭建好了

4.网站基础以及ctfd-whale配置

注:这个主要是配置题目分发时端口开启情况,因为你做一道题肯定是访问网站的一个端口

  • CTF配置

  • 管理员配置

  • 样式配置

  • 配置比赛开始时间

  • 直接finish 后

  • 进入管理员模板配置进行ctfd-whale配置

  • 设置方面,现给出下面的参数配置详情

属性配置Docker API URLunix://var/run/docker.sockFrp API IPfrpc的ip配置 172.1.0.3Frp API Portfrpc的端口配置 7400Frp Http Domain SuffixDocker API URL to connect(可填None)Frp Http Port80Frp Direct IP Address你的公网ip,本机即为127.0.0.1Frp Direct Minimum Port与之前frps最小端口呼应Frp Direct Minimum Port与之前frps最大端口呼应Max Container Count不超过最大-最小Max Renewal Times最大实例延时次数Frp config template填入frps的配置,只需填[common]Docker Auto Connect Containersctfd_frpc_1Docker Dns Setting可填机器内DNS,没有可填个外网DNS114.114.114.114Docker Swarm Nodeslinux-1 与前面swarm集群呼应Docker Multi-Container Network Subnet内网题大子网ip配置/CIDRDocker Multi-Container Network Subnet New Prefix每个内网题实例的CIDRDocker Container Timeout单位为秒Docker Auto Connect Networkctfd_frp-containers

  • 其中Frp config template配置内容如下,一会直接复制粘贴就行
[common]
token = randomme
server_addr = 172.1.0.4
server_port = 6490
pool_count = 200
tls_enable = true
admin_addr = 172.1.0.3
admin_port = 7400
  • 我的配置

5.汉化

  • 网上按照教程一顿操作,弄一个,网站瘫痪一次。最后发现汉化版本不一样,本次CTFd 的版本是2.3.1 因此,直接去github搜一下,果然找到了一个,只是一个半汉化。先凑合着用呗,其实你也可以自己汉化,就是把里面的样式自己改一下,比如在标题那里英文改成中文。

1.汉化链接

https://github.com/cxaqhq/ctfd-2.3.1

汉化包:ctfd-2.3.1-master.zip

下载下来之后,直接把

这两个替换为

这两个

2.汉化效果

6.发放题目

注:因为是swam模式本次仅仅测试了dynamic_ docker来添加题目 ,其他形式,请自测

1.添加一道web题目,如图这样配置

2.完成之后测试如下

因为拉取镜像需要时间,请耐性等待,多刷新浏览器

4.测试 网址

5.有的人可能就会问了,那我如何获取其他的题目,接下来就讲一讲。

  • 其实也没有啥,比如ctftraining/qwb_2019_supersqli 就是拉取了ctftraining的一道题目。我在CTFd源码里面没有找到,去github找了一下,发现了这个。
 https://github.com/CTFTraining/CTFTraining
  • 然后我尝试拉取其他的题目,比如 qwb_2019_upload 按照格式ctftraining/qwb_2019_upload进行拉取,果然成功了。大约6-8分钟拉取完一个题目,请耐心等待。

至于其他方式拉取题目我也测试了下,因为是swam模式,只有docker-dynamic这种方式比较简单。

  • standard 模式拉取ctftraining的试题也是可行的

   比如我在~目录下创建一个CTFTraining目录,git 一道题目

mkdir CTFTraining
cd CTFTraining/
git clone https://github.com/CTFTraining/0ctf_2016_unserialize.git
cd cd 0ctf_2016_unserialize/
  • 然后修改docker-compose.yml

  • 修改之后,启动

  • 这个时候你访问靶机地址:http://你的ip:8302   就能访问

  • 同样在CTFd中加入这道题目

[是兄弟就来打我](http://192.168.52.164:8302/)
  • 按照你自己的需求进行更改

flag  就是docker-compose.yml里面的flag

但是这种方式不太好,你打完这道题之后提交flag ,这道题目依然开着,只能手动关闭

6.附录:近年ctf writeup大全

https://github.com/ctfs/write-ups-2016
https://github.com/ctfs/write-ups-2015
https://github.com/ctfs/write-ups-2014
fbctf竞赛平台Demo
https://github.com/facebook/fbctf
ctf Resources
https://github.com/ctfs/resources
https://github.com/ctfwiki/ctf_game_history
https://github.com/SecWiki/ctf-hub
https://github.com/le31ei/ctf_challenges

7.问题报错以及解决办法

注释:本人遇到的问题还算比较少,因此报错以及解决办法多是搬运他人写的。

1.frpc日志报错:

解决方案:

仔细配置网络,网络配置的要求如下:

  • 将ctfd_frpc_1,frp-docker-for-ctfd-whale_frps_1,ctfd_ctfd_1这三个容器加入到ctfd_frp网络中
  • 这三个容器对应的IP如下:
  • ctfd_ctfd_1:172.1.0.2
  • ctfd_frpc_1:172.1.0.3
  • frp-docker-for-ctfd-whale_frps_1:172.1.0.4
docker network connect --ip 172.1.0.3 ctfd_frp ctfd_frpc_1
docker network connect --ip 172.1.0.4 ctfd_frp frp-docker-for-ctfd-whale_frps_1
docker restart ctfd_frpc_1 frp-docker-for-ctfd-whale_frps_1

加入ctfd_frp网络之后,重启完成之后再查看一下ctfd_frp网络,运行

 docker network inspect ctfd_frp

如果还是不行,灵活使用docker 的logs功能,查看是什么问题报错

docker logs <容器ID或名称>

2.Dockerfile的gevent报错

如果是以下报错,可以通过修改版本号解决

解决方案:

其他Dockerfile在构建的时候可能会出现gevent构建不成功的问题,有以下几种解决办法:

  1. 将镜像源替换为清华源
  2. 替换gevent版本
  3. 不指定gevent版本号

在CTFd目录下,修改requirements.txt的gevent

然后 docker-compose down 再启动 docker-compose up -d  不出意外应该就能解决问题

3.Dockerfile的world模块报错

解决方案:

将Dockerfile中的python和python-dev删掉,或者修改成python2或python3,python2-dev或python3-dev都可以

4.构建的时候,ctfd镜像无法启动

解决方案:

将docker-entrypoint.sh第一行修改为

#!/bin/bash

修改后重新docker-compose up -d即可

5.ctfd_ctfd_1容器一直在重启:

 ctfd_ctfd_1日志报错如下

解决方法:

出现时区问题,因此在requirements.txt中添加如下:

tzlocal==2.1

6.ctf_frpc_1容器一直重启

解决方案:

仔细配置网络,网络配置的要求如下:

  • 将ctfd_frpc_1,frp-docker-for-ctfd-whale_frps_1,ctfd_ctfd_1这三个容器加入到ctfd_frp网络中
  • 这三个容器对应的IP如下:
  • ctfd_ctfd_1:172.1.0.2
  • ctfd_frpc_1:172.1.0.3
  • frp-docker-for-ctfd-whale_frps_1:172.1.0.4
docker network connect --ip 172.1.0.3 ctfd_frp ctfd_frpc_1
docker network connect --ip 172.1.0.4 ctfd_frp frp-docker-for-ctfd-whale_frps_1
docker restart ctfd_frpc_1 frp-docker-for-ctfd-whale_frps_1

重启完成之后再查看一下ctfd_frp网络

docker network inspect ctfd_frp

如果还是不行,灵活使用docker 的logs功能,查看是什么问题报错

docker logs <容器ID或名称>

如果是以下报错

则重新配置frpc,编辑/CTFd/frpc/frpc.ini

[common]
token = randomme
server_addr = 172.1.0.4
server_port = 6490
pool_count = 200
tls_enable = true
admin_addr = 172.1.0.3 #这里千万千万别忘加,之前要被搞气死
admin_port = 7400

然后运行

docker restart ctfd_frpc_1

7.MySQL容器反复重启

解决方法:

因为当前目录下的.data文件中的MySQL日志与容器版本不匹配

解决方法是将.data文件删除,重新构建镜像

7.docker容器无法启动或者frp端口无法映射

如果docker容器无法启动或者frp端口无法映射可以进容器检查,确保docker api填写正确,如docker-compose.yml中写的unix:///var/run/docker.sock你也可以使用端口形式的api如官方示例:可以用IP:端口指定API

docker容器无法启动问题

进入容器检查:

docker exec -it <ctfd容器id> sh
/opt/CTFd# python
>>>import docker
>>>client=docker.DockerClient(base_url="unix:///var/run/docker.sock")
>>>client.images.list()

如果api正确会列出所有镜像

frp端口无法映射

其实检查可以顺便检查一下上面的,因为都在ctfd容器内 docker exec -it <ctfd容器id> sh

docker exec -it <ctfd容器id> sh
/opt/CTFd# python
>>>import requests
>>>requests.get("http://172.1.0.3:7400/api/reload")//即frp api的地址
返回
<Response [200]> #表示成功

如果frpc还是出现如下问题

requests.exceptions.ConnectionError: HTTPConnectionPool(host='172.1.0.3', port=7400): Max retries exceeded with url: /api/reload (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f8df919f850>: Failed to establish a new connection: [Errno 111] Connection refused'))

则重新配置frpc,编辑/CTFd/frpc/frpc.ini

[common]
token = randomme
server_addr = 172.1.0.4
server_port = 6490
pool_count = 200
tls_enable = true
admin_addr = 172.1.0.3 #这里千万千万别忘加,之前要被搞气死
admin_port = 7400

然后运行docker restart ctfd_frpc_1(这里再看frpc.ini会发现内容更新了,admin配置没了,不用担心)

再进容器检查requests.get("http://172.1.0.3:7400/api/reload")应该就可以