• 上次搭建 CTFd 还是好久好久之前(2年叭),前阵子由于院校比赛需要又捡起来了,这次顺便将动态靶场搭建(Whale)和动态 Flag 都搞上,经过漫长的百度流程,还是总结出了蛮多东西的。

引言

  • 之前有按照网上的文章从零开始搭建 CTFd + Whale,经过了两天的努力侥幸成功了两次(淦),其它都会出现动态靶场出现地址,但是 CTFd 不会拉取镜像的问题(QAQ)。

  • 本来已经放弃,又看到 VaalaCat 师傅的文章可以一键启动,尝试了两遍没问题,写一份文档备用。

  • VaalaCat 师傅文章地址:传送门

  • Linux 系统版本:Ubuntu 18.04(众多师傅的反馈发现 Ubuntu 20 会出现不可预测的 bug,我之前也是用 Ubuntu 20.04)

Ubuntu 靶机安装

  • VM 安装过程略。
  • 登录后修改下 root 密码:
1
sudo passwd root
  • 进行 SSH 安装,目前靶机用不到 VMware Tools:
1
apt install -y ssh
  • 因为要后续要进行多个软件安装,更新下 apt 源:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apt install -y vim
vim /etc/apt/sources.list

# aliyun
deb https://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse

deb https://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse

deb https://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse

deb https://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
  • 更新 apt 源:
1
apt-get update -y
  • 由于需要 curl 下载文件,安装一下:
1
apt-get install -y curl

Docker 环境安装

  • 由于动态靶机是使用 docker 实现的,所以首先要准备安装一下 docker(这一步耗时较长):
1
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
  • 然后还要准备 docker-compose(建议使用 proxychains 代理下载):
1
2
3
apt-get install -y python3-pip

pip3 install docker-compose -i https://pypi.tuna.tsinghua.edu.cn/simple
  • 这时会出现报错信息:
1
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-5ydi0mj7/cryptography/
  • 百度了一下,需要更新 pip:
1
python3 -m pip install --upgrade --force pip
  • 再次安装 docker-compose 即可。

Docker 集群配置

  • Whale 插件使用的是集群的模式,所以这里要创建一个单个服务器的集群:
1
docker swarm init
  • 然后将这个服务器加入集群:
1
docker node update --label-add='name=linux-1' $(docker node ls -q)
  • 最后要准备的是给 docker 更换镜像源,不然会很龟速,修改文件 /etc/docker/daemon.json:
1
2
3
{
"registry-mirrors": ["https://y5u7p3c7.mirror.aliyuncs.com"]
}
  • 重启 docker 生效:
1
2
systemctl daemon-reload
systemctl restart docker

CTFd + Whale 安装

  • 使用 VaalaCat 师傅整合好的 ctfd:
1
git clone https://github.com/Un1kTeam/CTFd --depth=1
  • 修改 frp 配置文件(看自己需求,不变就不动):
1
2
3
4
5
6
# frps.ini
[common]
bind_port = 7000
vhost_http_port = 9123
token = your_token
subdomain_host = node.vaala.ink
1
2
3
4
5
6
7
# frpc.ini
[common]
token = your_token
server_addr = 172.1.0.3
server_port = 7000
admin_addr = 172.1.0.4
admin_port = 7400
  • 插件初始化、更新或检查子模块:
1
git submodule update --init
  • 启动 CTFd(巨慢,我用了半小时):
1
docker-compose up -d
  • 查看下 5 个容器是否正常运行:
1
2
3
4
5
6
7
8
9
docker ps -a

root@docker:/opt/CTFd# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
47daf4dfd0b4 glzjin/frp:latest "/usr/local/bin/frpc…" 3 minutes ago Up 3 minutes ctfd_frpc_1
07dc9cfaab10 ctfd_ctfd "/opt/CTFd/docker-en…" 3 minutes ago Up 3 minutes 0.0.0.0:9124->8000/tcp, :::9124->8000/tcp ctfd_ctfd_1
501401fb07ff mariadb:10.4.12 "docker-entrypoint.s…" 3 minutes ago Up 3 minutes ctfd_db_1
39163c2ad93e glzjin/frp "/usr/local/bin/frps…" 3 minutes ago Up 3 minutes 0.0.0.0:9123->9123/tcp, :::9123->9123/tcp, 0.0.0.0:9125-9129->9125-9129/tcp, :::9125-9129->9125-9129/tcp ctfd_frps_1
0cc9acac2e4c redis:4 "docker-entrypoint.s…" 3 minutes ago Up 3 minutes ctfd_cache_1

Whale 配置

  • 新版 CTFd 的 Whale 和旧版不同,做了板块区分,这里 VaalaCat 师傅已经配好了。
  • 左侧 Docker 菜单栏如果没有特殊需求无需更改,只需更改左侧 Frp 菜单栏中的内容:
内容
Http Domain Suffix node.vaala.ink ,这里填写使用 HTTP 方式访问靶机的泛解析域名
Http Port 9123,这里填写 frps 中的 vhost_http_port,该端口为 HTTP 方式靶机访问的端口
Direct IP Address chive.vaala.cloud,这里填写服务器 IP,用于显示 Direct 方式访问的题目的IP
Direct Minimum Port 9125,这里填写用于动态靶机 Direct 方式的开始端口
Direct Maximum Port 9129,这里填写最大端口
  • 只需要填入 Direct IP Address 即可。
  • 最后点一下更新就可以保存配置了,填写完成后新建题目测试是否成功,按下表新建题目,表中没有提到的保持默认就好:
内容
Choose Challenge Type Dynamic docker
Name test
Category test
Docker Image vaalacat/push_f12
Frp Redirect Type Direct或者 Http
Frp Redirect Port 80
Initial Value 1
Decay Limit 1
Minimum Value 1
Score Type dynamic score
  • 新建题目过后点击启动,然后等待靶机创建,可以在服务器中 docker ps -a 查看是否启动,若启动成功则搭建完成。
Whale_0830 Whale_0830 (2)

端口修改

  • VaalaCat 师傅仓库中所使用的端口一共有三类,需要修改平台信息请按照以下文件位置修改:
    • 9123(一般用不到,都采用直连):
      • 默认 http 模式靶机访问端口,需要修改 frps.ini、后台中的 Http Portdocker-compose.yml
    • 9124:
      • 默认为 ctfd 端口,需要修改 docker-compose.yml
    • 9125-9129:
      • 动态靶机 Direct 模式端口,需要修改 docker-compose.yml、后台中的端口范围

后记

Docker Swarm

  • 在一段时间不使用 CTFd 后,使用 docker-compose up -d 会出现如下报错:
1
2
3
root@docker:/opt/CTFd# docker-compose up -d
......
ERROR: This node is not a swarm manager. Use "docker swarm init" or "docker swarm join" to connect this node to swarm and try again.
  • 解决方法如下:
1
2
3
docker swarm leave
docker swarm init
docker node update --label-add='name=linux-1' $(docker node ls -q)

前端汉化

  • 由于该大佬的 CTFd 版本是 3.5.0,市面上没有完美适配的汉化包,为了避免意外,将前端汉化一下就好:
  • 项目地址:https://github.com/Gu-f/CTFd_chinese_CN
  • 下载完成后,我们只需要 CTFd-3.4.1/CTFd/themes/core 文件夹即可,重命名为 core-cn
  • 将该文件放到 Ubuntu 的 CTFd/CTFd/themes 即可,在后台进行切换:

image-20231031135743897

端口开放

  • 由于公司映射的端口比较特殊,且访问量较大,这里直接修改开放端口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
frps:
image: glzjin/frp
restart: always
volumes:
- ./conf/frp:/conf
entrypoint:
- /usr/local/bin/frps
- -c
- /conf/frps.ini
ports:
- 40001-41000:40001-41000 # 映射direct类型题目的端口
- 9123:9123 # 映射http类型题目的端口
networks:
default:
frp_connect:
ipv4_address: 172.1.0.3

路由删减

  • 默认打开大致会产生几个路由表项:
1
2
3
4
5
6
7
8
9
10
root@docker:/opt/CTFd# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default _gateway 0.0.0.0 UG 100 0 0 ens32
10.10.8.0 0.0.0.0 255.255.255.0 U 100 0 0 ens32
link-local 0.0.0.0 255.255.0.0 U 1000 0 0 ens32
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker_gwbridge
172.19.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-04dc3f4f5258
172.20.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-ac6c0698e9e8
  • 经过简单的尝试,发现若是删除 172.17,就会生成 172.21,所以哪个网段冲突就删除哪个,不影响使用:
1
2
3
4
route del -net 172.17.0.0 netmask 255.255.0.0
route del -net 172.18.0.0 netmask 255.255.0.0
route del -net 172.19.0.0 netmask 255.255.0.0
route del -net 172.20.0.0 netmask 255.255.0.0
  • 使用 docker-compose up -d 后重新生成:
1
2
3
4
5
6
7
8
root@docker:/opt/CTFd# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default _gateway 0.0.0.0 UG 100 0 0 ens32
10.10.8.0 0.0.0.0 255.255.255.0 U 100 0 0 ens32
link-local 0.0.0.0 255.255.0.0 U 1000 0 0 ens32
172.21.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-202551e5b1ed
172.22.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-2d92069faa0f

CTFd Update Del

  • 每次进后台都会看见更新提示,很烦,直接删了。

image-20231031145848274

  • 该文件需要进入 CTFd/CTFd/themes/admin/templates/base.html 进行修改,把下面这段内容删除:
1
2
3
4
5
6
7
8
9
<div class="container-fluid bg-warning text-center py-3">
<div class="row">
<div class="col-md-12">
<a class="btn btn-warning" href="{{ get_config('version_latest') }}">
A new CTFd version is available!
</a>
</div>
</div>
</div>
  • 世界清净了:

image-20231031150024930

容器题目

  • 建议创建题目时不要直接填镜像 ID,先拉取不然创建很慢。

开启自启动

  • 创建一个 ctfd.sh 放在根目录,内容如下:
1
2
#!/bin/bash
docker-compose -f /opt/CTFd/docker-compose.yml up -d
  • 给个执行权限:
1
chmod +x /root/ctfd.sh
  • 创建服务单元:
1
vim /etc/systemd/system/ctfd.service
  • 内容如下:
1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=CTFd Start
Requires=docker.service
After=docker.service

[Service]
Restart=always
ExecStart=/root/ctfd.sh

[Install]
WantedBy=default.target
  • 重载一下:
1
systemctl daemon-reload ; systemctl enable ctfd.service ; systemctl start ctfd.service
  • 验证一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@docker:~# systemctl start ctfd.service
root@docker:~# systemctl status ctfd.service
● ctfd.service - CTFd Start
Loaded: loaded (/etc/systemd/system/ctfd.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2023-11-09 23:57:35 CST; 212ms ago
Main PID: 9098 (ctfd.sh)
Tasks: 2 (limit: 2281)
CGroup: /system.slice/ctfd.service
├─9098 /bin/bash /root/ctfd.sh
└─9099 /usr/bin/python3 /usr/local/bin/docker-compose -f /opt/CTFd/docker-compose.yml up -d

11月 09 23:57:35 docker systemd[1]: Started CTFd Start.
11月 09 23:57:35 docker ctfd.sh[9098]: /usr/local/lib/python3.6/dist-packages/paramiko/transport.py:32: CryptographyDeprecationWarning: Python 3.6 is no longer supported by the Python core team. Therefore
11月 09 23:57:35 docker ctfd.sh[9098]: from cryptography.hazmat.backends import default_backend
11月 09 23:57:35 docker ctfd.sh[9098]: The Docker Engine you're using is running in swarm mode.
11月 09 23:57:35 docker ctfd.sh[9098]: Compose does not use swarm mode to deploy services to multiple nodes in a swarm. All containers will be scheduled on the current node.
11月 09 23:57:35 docker ctfd.sh[9098]: To deploy your application across the swarm, use `docker stack deploy`.

Web 动态链接

  • CTFd_Whale 插件默认情况下是没有动态链接的,非常难受,这里直接修改文件。
  • 文件路径:/opt/CTFd/CTFd/plugins/ctfd-whale/assets/view.js
  • 原文内容:
1
2
3
4
5
6
7
8
9
10
'<div class="card" style="width: 100%;">' +
'<div class="card-body">' +
'<h5 class="card-title">Instance Info</h5>' +
'<h6 class="card-subtitle mb-2 text-muted" id="whale-challenge-count-down">Remaining Time: ' + response.remaining_time + 's</h6>' +
'<h6 class="card-subtitle mb-2 text-muted">Lan Domain: ' + response.lan_domain + '</h6>' +
'<p class="card-text">' + response.user_access + '</p>' +
'<button type="button" class="btn btn-danger card-link" id="whale-button-destroy" onclick="CTFd._internal.challenge.destroy()">Destroy this instance</button>' +
'<button type="button" class="btn btn-success card-link" id="whale-button-renew" onclick="CTFd._internal.challenge.renew()">Renew this instance</button>' +
'</div>' +
'</div>'
  • 现修改为:
1
2
3
4
5
6
7
8
9
'<div class="card" style="width: 100%;">' +
'<div class="card-body">' +
'<h5 class="card-title">Instance Info</h5>' +
'<h6 class="card-subtitle mb-2 text-muted" id="whale-challenge-count-down">Remaining Time: ' + response.remaining_time + 's</h6>' +
'<p class="card-text">' + '<a target="_blank" href="http://' + response.user_access + '">' + 'http://' + response.user_access + '</a>' + '</p>' +
'<button type="button" class="btn btn-danger card-link" id="whale-button-destroy" onclick="CTFd._internal.challenge.destroy()">Destroy this instance</button>' +
'<button type="button" class="btn btn-success card-link" id="whale-button-renew" onclick="CTFd._internal.challenge.renew()">Renew this instance</button>' +
'</div>' +
'</div>'
  • 如果是比较新的版本,那修改成如下的样子:
1
2
3
$("#user-access").html(
'<a target="_blank" href="http://' + response.user_access + '">' + "http://" + response.user_access + "</a>"

CTFd 用户批量导入

  • 最近也是有个比赛,组队人员也是比较多的,百度了个脚本改了改:
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
import requests
import json

url = 'url'
Cookie = input('Input Session:')
CSRF = input('Input CSRF-Token:')
if not Cookie:
Cookie = 'Session'
if not CSRF:
CSRF = 'CSRF-Token'
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
'Cookie': Cookie,
'Referer': url + '/admin/users/new',
'CSRF-Token': CSRF
}

user = []
passwd = []
file = open('user.txt', 'r', encoding="utf-8")
file1 = open('passwd.txt', 'r', encoding="utf-8")

for i in file.readlines():
user.append(i[:-1])
for j in file1.readlines():
passwd.append(j[:-1])

for k in range(len(user)):
data = {
"name": user[k],
"email": str(k) + "1@qq.com",
"password": passwd[k],
"type": "user",
"verified": "false",
"hidden": "false",
"banned": "false"
}
response = requests.post(
url = url + "/api/v1/users",
headers=headers,
data=json.dumps(data)
)
if "true" in response.text:
print("User:" + user[k] + " -- Pass:" + passwd[k] + " -- Email:" + str(k) + "1@qq.com")
else:
print("用户创建失败!")

file.close()
file1.close()