前言

也是很久没有更新自己的博客了,正好前些天老师想让我搭建一个集成web题的靶场给学生测试,然后我就想用CTFd来做靶场,有动态靶机 速度也不错。

但是当我搭建时我参考了很多网上的文章,总是会出现一些问题,运维是一件很痛苦的事情…搭建过程并不顺利。

因此当我搭建完,就想记录下搭建要记录的地方,我会结合看到的实用文章的东西来讲,也顺便让大家参考一下,少走一些弯路!故此这篇文章诞生。

(参考文章贴文末了)

以下是我搭建完的平台:(两张图片都是自己仿着buu首页手画的,马马虎虎hh)

准备工作

首先要明白搭建CTFd并不困难,但注意如果你的平台想要使用动态靶机这个功能,则需要安装CTFd-Whale插件,安装这个插件需要有一堆环境 要配置网络frp 使用到docker等,这些是比较麻烦的操作。

因此这里我就讲讲CTFd与CTFd-Whale插件的并同部署的过程和一些出现的问题。

注意事项

  1. 这里安装的部署环境都是用赵师傅现成的,所以配置文件按照我教程的来即可,大多不需要变化 按自己需要来!
  2. *读者们在复制配置文件的时候,请自行将注释去掉。注释是为了方便解读,要注意的地方我也会提示大家。
  3. 保持一颗耐心、积极的心态,并且保险起见,最好每完成一个节点就打一个快照 。

基本环境安装

1.确保DNS无误 可以出网(附一个我ubuntu dns会一直刷新 修复的办法 有需要可以自取)

/etc/systemd/resolved.conf

DNS=8.8.8.8 114.114.114.114 #修改DNS为这个

sudo systemctl restart systemd-resolved
sudo systemctl enable systemd-resolved

sudo mv /etc/resolv.conf /etc/resolv.conf.bak
sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf

2.安装 python3

3.安装好 Docker 和 Docker-Compose

Docker使用国内的daocloud一键安装命令

curl -sSL https://get.daocloud.io/docker | sh

docker-compose使用pip安装

pip install docker-compose

安装成功用-v检查一下

*4. apt换源 已经换过了可以略过这一步

echo deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse > /etc/apt/sources.list
echo deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse >>/etc/apt/sources.list
echo deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse >>/etc/apt/sources.list
echo deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse >>/etc/apt/sources.list
echo deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse >>/etc/apt/sources.list
echo deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse >>/etc/apt/sources.list
echo deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse >>/etc/apt/sources.list
echo deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse >>/etc/apt/sources.list
echo deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse >>/etc/apt/sources.list
echo deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse >>/etc/apt/sources.list

apt-get update

部署需要

(这里如果github下载太慢可以真机下载完后拖入虚拟机)

1.下载CTFd源码(这里是赵师傅已经换源后的源码)

git clone https://github.com/glzjin/CTFd.git

注:版本是v2.3.1,修改后的版本问题比较少,但是此版本支持的主题和汉化很少,几乎没有。(所以还是比较推荐CTFd3.0以上的版本)

2.下载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
rm -rf frp_0.29.0_linux_amd64.tar.gz

3.下载CTFd-Whale

git clone https://github.com/glzjin/CTFd-Whale ctfd-whale

4.下载docker的frps

git clone https://github.com/glzjin/Frp-Docker-For-CTFd-Whale frp-docker-for-ctfd-whale

确保CTFd-Whale docker-frps文件夹都是小写!

下载完后你的桌面应该是这四个文件夹。

部署过程

1.docker设置集群

这里一定要先记得设置集群,不然后面会报错。

docker swarm init

docker node ls

docker node update --label-add name=linux-1 <节点 ID>


如果你输入 docker swarm init 出现以下报错,则代表你先前已经开过了


使用 docker swarm leave --force 删除后,重复以上操作即可。

2.将插件ctfd-whale放入ctfd文件夹

mv ctfd-whale CTFd/CTFd/plugins/

3.为docker安装frps

注意这里frp不是装在你的服务器 你的虚拟机里的!是安装在你的docker容器里。

这里进入frp-docker-for-ctfd-whale 为docker进行安装

cd frp-docker-for-ctfd-whale
docker-compose up -d

第一次安装需要等待一些时间。


看到done就是安装成功了。

可以用 docker ps 来看看docker是否运行。


可以看到这里status是up,代表正在运行。

这一步到这里就结束了,如果要修改配置文件请继续往下看,不需要则可以跳至下一步了。

docker-compose.yml

version: '2'

services:
frps:
image: glzjin/frp:latest
restart: always
volumes:
- ./frp:/conf/
entrypoint:
- /usr/local/bin/frps
- -c
- /conf/frps.ini
ports:
- "28000-28100:28000-28100" #这里是动态靶机的开放端口段,如需修改开放端口这里要修改。
- "6490:6490" #这里如果要修改端口则必须与frps.ini配置一致
networks:
default:

networks:
default:

frps/frps.ini

[common]
bind_port = 6490 #上面的端口如果改了,这里也要跟着改
token = randomme #token不用动

*复制请注意将注释删除。

4.CTFd配置

为了和插件更好的兼容,这里要修改CTFd的docker配置文件 还要新增文件。

cd到 CTFd/ 路径下,对以下文件进行修改:

1.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:
# This command is required to set important mariadb defaults
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

2.nginx/http.conf

新建nginx文件夹,并创建http.conf文件,并将以下内容添加进去。

mkdir nginx
vim 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;
}
}
}

添加完成:wq保存即可。

3.frpc/

CTFd/目录下创建frpc文件夹,并将解压的frp_0.29.0_linux_amd64文件夹移动文件。

mkdir frpc
cd ../frp_0.29.0_linux_amd64
mv frpc frpc.ini frpc_full.ini LICENSE ../CTFd/frpc

进入到frpc文件夹,对frpc.ini进行修改:

cd ../CTFd/frpc
vim 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

这里server_port必须与frpc.ini配置一致,且一定要添加admin_addr和admin_port,这里都已经满足条件了。

这里配置文件一定不能有注释,否则会导致docker运行一直restart


像我这样配置完后保存退出即可。

4.requirements.txt

官方的requeirements.txt很多版本会存在问题,所以替换成以下文件即可:

Flask==1.1.2
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
jinja2==2.11.3
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
gevent==21.12.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
markupsafe==1.1.1
marshmallow==2.20.2

5.配置Dokcerfile

编辑CTFd/路径下的Dockerfile文件,注意这里的配置文件是v2.x都适用的,到3.0之后的新版本要换一个新写法。

版本v2.x写法

FROM python:3.7-alpine
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && \
apk update && \
apk add linux-headers libffi-dev gcc make musl-dev py-pip mysql-client git openssl-dev #这里注意1
RUN adduser -D -u 1001 -s /bin/bash ctfd

WORKDIR /opt/CTFd
RUN mkdir -p /opt/CTFd /var/log/CTFd /var/uploads

COPY requirements.txt .

RUN pip install -r requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/ #这里注意2

COPY . /opt/CTFd

RUN for d in CTFd/plugins/*; do \
if [ -f "$d/requirements.txt" ]; then \
pip install -r $d/requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/ ; \
fi; \
done; #同样注意2

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的写法(v2.x的可以略过了)

FROM python:3.7-slim-buster
WORKDIR /opt/CTFd
RUN mkdir -p /opt/CTFd /var/log/CTFd /var/uploads

# hadolint ignore=DL3008
RUN echo 'deb http://mirrors.aliyun.com/debian/ buster main non-free contrib \
deb http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib \
deb http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib \
deb-src http://mirrors.aliyun.com/debian/ buster main non-free contrib \
deb-src http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib \
deb-src http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib \
deb http://mirrors.aliyun.com/debian-security/ buster/updates main non-free contrib \
deb-src http://mirrors.aliyun.com/debian-security/ buster/updates main non-free contrib'> /etc/apt/sources.list && \
apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
default-mysql-client \
python3-dev \
libffi-dev \
libssl-dev \
git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt /opt/CTFd/

RUN pip install -r requirements.txt -i http://mirrors.aliyun.com/pypi/simple/ --no-cache-dir

COPY . /opt/CTFd

# hadolint ignore=SC2086
RUN for d in CTFd/plugins/*; do \
if [ -f "$d/requirements.txt" ]; then \
pip install -r $d/requirements.txt -i http://mirrors.aliyun.com/pypi/simple/ --no-cache-dir; \
fi; \
done;

RUN adduser \
--disabled-login \
-u 1001 \
--gecos "" \
--shell /bin/bash \
ctfd
RUN chmod +x /opt/CTFd/docker-entrypoint.sh \
&& chown -R 1001:1001 /opt/CTFd /var/log/CTFd /var/uploads

USER 1001
EXPOSE 8000
ENTRYPOINT ["/opt/CTFd/docker-entrypoint.sh"]


编辑完退出即可。

6.开启部署

准备工作已就绪,剩下的直接交给docker去build构建即可

一定要切换到 CTFd 路径下,执行:(这一句话等同于docker-compose build + docker-compose up -d)

docker-compose up -d --build

(有安装卡住的部分ctrl+c后重新运行下就好)


如图所示,有WARNING即为在集群网络类,是正常情况


这边显示done就是开启成功了,等待安装完成后,查看容器是否运行:

docker ps


可以看到这里平台已经映射到了真机的8000端口了

访问 http://ip:8000/ 即可访问搭建好的CTFd平台了。

7.配置CTFd

1.初始化

刚搭好平台,要初始化设置一下,这里跟着我设置即可。

设置错了也没关系,后续在后台也能修改。




2.配置CTFd-Whale

设置好后进入后台 Admin Panel 找到 Plugins -> ctfd-whale


属性 配置
Docker API URL unix://var/run/docker.sock
Frp API IP frpc的ip配置
Frp API Port frpc的端口配置
Frp Http Domain Suffix Docker API URL to connect(可填None)
Frp Http Port 80
Frp Direct IP Address 你的公网ip
Frp Direct Minimum Port 与之前frps最小端口呼应
Frp Direct Minimum Port 与之前frps最大端口呼应
Max Container Count 不超过最大-最小
Max Renewal Times 最大实例延时次数
Frp config template 填入frps的配置,只需填[common]
Docker Auto Connect Containers ctfd_frpc_1
Docker Dns Setting 可填机器内DNS,没有可填个外网DNS
Docker Swarm Nodes linux-1 与前面swarm集群呼应
Docker Multi-Container Network Subnet 内网题大子网ip配置/CIDR
Docker Multi-Container Network Subnet New Prefix 每个内网题实例的CIDR
Docker Container Timeout 单位为秒

按照图片的来配置即可


配置好了后记得点击 update 更新

8.设置docker网络

此时运行 docker ps 能发现frpc在一直重启


这是因为我们还没有配置网络

运行 docker network ls


显示ctfd_frp-containers

运行 docker network inspect ctfd_frp 查看网络情况


能发现这里只有ctfd,没有接入frpc和frps,先用docker ps分别找到frps和frpc的ID


我这里两个是 frpc:6a7a98e8cd38 frps:5192ccf2c8cb

然后使用docker network connect ctfd_frp <frp容器id> 连接

docker network connect ctfd_frp 6a7a98e8cd38
docker network connect ctfd_frp 5192ccf2c8cb

此时再查看网络情况docker network inspect ctfd_frp 能发现多了frpc和frps

重启docker网络:

docker restart ctfd_frpc_1 frp-docker-for-ctfd-whale_frps_1


最后docker ps验证下,能发现frpc可以一直运行了。


修复成功。

9.部署完毕,新建题目测试

如果做到这一步 过程都成功了,那么恭喜你已经成功搭建了CTFd和CTFd-Whale插件了,现在进行题目的测试。

后台创建题目:


然后贴下赵总的图:




这里直接用 ctftraining/qwb_2019_supersqli dockerhub的镜像来测试

创建成功后 再添加flag和题目显示情况即可 (这些选项在后台的题目详情也可以添加、修改)

完成后就在 Challenges 点击题目测试


开启靶机,访问靶机端口(第一次开启可能会等的比较久):


可以看到成功开启来了,docker ps也能看到docker运行状况:

一键部署脚本

这个脚本是我自己写的,为了方便一键开启CTFd,适用于访问ip不到网站的情况下,使用python3运行大概率可以开启服务。

临时写的,比较糙 不过效果还可以 大多都是能开起来的。

使用方法

将脚本放入你的CTFd文件夹,使用python3运行即可

python3 start.py

start.py

import os
import re

def shutdown():
cmd = 'docker-compose down'
os.system(cmd)

def start():
cmd = "docker-compose up -d"
os.system(cmd)

def check():
cmd = 'docker ps'
r = ''
for i in os.popen(cmd):
#print(i)
r += i
return r

def network():
c1 = "docker network inspect ctfd_frp"
r = ''
for i in os.popen(c1):
#print(i)
r += i
return r

def connect(id):
c1 = f"docker network connect ctfd_frp {id}"
os.system(c1)

def restart():
c1 = 'docker restart ctfd_frpc_1 frp-docker-for-ctfd-whale_frps_1'
os.system(c1)


os.system('docker network disconnect -f ctfd_frp frp-docker-for-ctfd-whale_frps_1')

print(f'[*] Restarting!')

shutdown()
start()
content = check()
ctfdid = re.findall('\n(.*?) ctfd_ctfd',content)[-1]

print(f'[*] Now checking network!')

cmd = f'docker exec -it {ctfdid} python -c \'import requests;r = requests.get("http://172.1.0.3:7400/api/reload");print(r)\''
r = ''
for i in os.popen(cmd):
r += i
if 'Connection refused' in r:
print(f'[*] network fixing')
c2 = network()
if 'frp-docker-for-ctfd-whale_frps_1' not in c2:
content = check()
frpsid = re.findall('(.*?) frp-docker-for-ctfd-whale_frps_1',content)[0].split('glzjin/frp:latest')[0].strip()
connect(frpsid)
print(f'[+] frps append in ctfd success!')
restart()
f = open('frpc/frpc.ini','w')
main="""[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
"""
f.write(main)
f.close()
print(f'[+] Config(frpc.ini) overwriting success!')
os.system('docker restart ctfd_frpc_1')
r = ''
for i in os.popen(cmd):
r += i
if '<Response [200]>' in r:
print('[+] Start Success!')

一些值得注意的地方

帮助调试的命令

docker ps #获取容器ID
docker logs <ID> #查看一个docker容器的运行日志

*如果容器一直处于restart的状态,使用这条命令查看其运行情况,再根据报错去网上查询,非常有效且高效!

docker exec -it <ID> sh #进入一个容器,查看内部情况
docker network prune -f #删除未使用的网络,适用于重启服务某些网络未关闭的情况

疑难杂症

如果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
/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")应该就可以了<Response [200]>

frp端口冲突

如果发现frp端口冲突,请检查本地frpc或者frps服务,frp是在docker上不是在本地的!

systemctl stop frpc
systemctl stop frps

部署成功

如果到这一步都没有出现问题,非常恭喜你,CTFd和动态靶机部署已经成功搭建。

这篇文章就到此结束了,下一篇文章是关于平台的后续工作:平台的美化、优化、题目镜像制作等。

感谢你的观看,希望能帮到你!也可以在这篇文章评论进行讨论问题

参考文章

这里我贴出几个对我有帮助的文章,非常详细!我的这篇文章都是结合各位师傅的教程和经验总结出来的。

https://err0r.top/article/CTFD/ 

https://www.zhaoj.in/read-6333.html(赵师傅官方文章)

https://www.yuque.com/dat0u/ctfd