** 建议安装前服务器拍个快照,不行还原回去多试几次 **

搭建

初始化集群

docker swarm init 如果不行加个 –force-new-cluster
docker node update --label-add='name=linux-1' $(docker node ls -q) 加入集群别名 linux-1

下载 CTFd 并安装插件

1
2
3
git clone https://github.com/yichen115/CTFd.git --depth=1
cd CTFd
git submodule update --init

这里提醒一下,docker-compose.yml 里的 frps 端口映射范围提前根据自己需要改一下,后面题目端口设置需要在这个范围内,不然就没法访问

image.png
image.png

拉起来

docker-compose up -d

访问与设置

等上面拉起来之后访问进入(端口取决于 docker-compose.yml 我这个是 80),setup 自己随便设置
在 Admin Panel 的 Whale 的 Frp 的 Direct IP Address 是你服务器的 IP

Direct Minimum Port 动态靶机 Direct 方式可用最小端口
Direct Maximum Port 动态靶机 Direct 方式可用最大端口

可用端口一定要在 docker-compose.yml 里的 frps 端口映射范围内

维护

平台添加题目

在后台的 challenges 那个页面,点加号,如果是想添加动态 flag 的题目:左侧类型选择 dynamic_docker,然后 Docker Image 直接写 dockerhub 上面的镜像名即可,比如:ctftraining/qwb_2019_supersqli,想自己做成镜像用的话可以参考本文档的出题部分,Frp Redirect Type 选择 Direct,Frp Redirect Port 是 docker 镜像开的端口

flag 格式

在后台的 whale 界面的 Challenges 选项更改
默认是:"flag{"+uuid.uuid4()|string+"}"
样式:flag{67031515-682b-4f59-9678-bc43c7674096}

前缀随便改了,想要把中间的 - 去掉,可以在后面加个 .hex
"aeuctf{"+uuid.uuid4().hex|string+"}"
就是 aeuctf{cea24c8b0f0949e6887d3348251f2fca}

忘记管理员密码

随便注册一个账号
docker ps 查看容器,进 mariadb:10.4.12
mysql -u root -p 密码是 ctfd
在数据库 ctfd,user 表,把刚才注册的用户 type 字段改为 admin,然后登录就行了

[×]题目分页与检索

想要实现类似 BUU 那种可以分页的,网上暂时没找到合适的

image.png
image.png

https://gitee.com/kee1ong/ctfd-pages-theme
有个差不多的,但是不行,npm 简直魔鬼,各种报错,暂时放弃 😅
assets/js/pages/challenges.js 和 templates/challenges.html

Read-only file system 解决

想进 ctfd 的 docker 换个主题啥的,结果给我说 Read-only file system
感觉是 docker-compose.yml 的第 22 行有个 :ro,因为它的基础镜像是没有这个 Read-only 的,把这个去掉应该就行了 🤦‍♂️

更新:确实是 :ro 的问题,删了即可 😀

换主题

解决了 Read-only file system 直接找个适配 CTFd 版本的主题,去 docker 里面 clone 下来
然后后台更换即可,

MARK 几个适配了 3.3.0 的主题:

我用的是 ctfd-neon-theme 为了下载速度,我同步到了 gitee

image.png
image.png
image.png
image.png
image.png
image.png

CTFd-theme-pixo fork 的 gitee 的链接
这个对动态靶机支持不好,不过这字体是真的好看,看看能不能两个结合一下 😋

image.png
image.png
image.png
image.png

QQ 群机器人

[NO]以爬虫的方式爬取 api

https://github.com/yichen115/ctfd-qq-bot~ 我这个只是一个小爬虫,主要是配合 ~go-cqhttp~ 项目
根据 ~https://docs.go-cqhttp.org/~ 找一个适合自己服务器的 release 版本

放在服务器上,进行配置,首次运行会生成一个文件,里面填写用来作为机器人的 qq 账号密码之类的,然后后台运行就行了,网上挺多方法,比如运行着 ctrl+z 然后 bg,还有 nohup 这种的
~~nohup python3 ctfd-bot.py &~~
ctfd-bot.py 是用 python3 写的,先安装对应的库,打开修改对应的参数,然后也是后台运行起来即可,需要关掉直接 ~~~kill -9 pid~~
时间长了可能会自动挂掉,建议写个计划任务重启一哈

submissions 分页机器人爬不到数据

~ 最新版脚本已解决,仅记录解决过程 ~
机器人跑了一阵突然只显示新用户注册不显示解题成功了,看了下,并不是脚本的问题,可以获取 /api/v1/submissions 的数据,问题出在 CTFd 的这个 api 不知道为啥不更新数据了,新的解题记录后台虽然能看,但是不显示在 /api/v1/submissions 页面上了,还没找到原因,后台看到的数据跟 mariadb:10.4.12 数据库中的数据是一致的,也就是说这个 api 不是查的数据库的数据?噗,找到原因了,确实请求的是数据库的数据,但他是分页的,submissions 一页只能看 20 个数据,users 一页只能看 50 个数据,害,但凡好好看看 JSON 数据就看出来了

image.png
image.png

脚本没想好咋改,本来想着每次总数取余 20 为 0 的时候就加一页,但是如果一直是 20 的倍数又麻烦了,总之好麻烦,简直是给自己出了一个编程题

突然发现可以直接找到 pages,那就是目前最大的页数了,就用那个即可 😉,可以获取页数了,但是有个问题,不知道为啥 async def deal_attemp_list 函数总是莫名其妙的不执行,我又傻了,原来是不知道啥时候把请求机器人那个注释删掉了,他一直在那请求机器人了

现在问题就只剩下切换页面的时候会漏下该页面第一个,怎么又成了编程题了 🤪 原来脚本是获取当前页面的 JSON 中数组的长度,跟之前的比较,如果之前的小于当前的,就把这之间差的当作是新的提交,但更新页面之后当前数组长度为 1,之前的是 20 的倍数,就没法输出了。害,麻了,不多想了,加个判断条件当前是 1 的时候输出一下就行了

[YES]添加机器人接口

Github 源码中已添加,可直接使用
想到一个在源码中添加接口的解决方法,不用爬虫了,爬虫服务容易宕掉不稳定,机器人还是用 go-cqhttp 项目
在 CTFd\api\v1\challenges.py 的 ChallengeAttempt 类中,有一处是根据插件返回的正确与否选择是否向数据库写入 solves 的记录,在这之前或之后直接加一句访问机器人服务的脚本就行了,类似这样

image.png
image.png

但是每个人或者每次修改机器人服务地址以及自定义语句的时候都得修改源码重启不太优雅,可以在后台预留一个接口,在后台设置相关的参数。
机器人服务需要有以下几个参数,准备在 config 数据表里面留着
1、url 地址,字段:boturl
2、消息内容,字段:bottext
3、是否启用 bot,字段:bot
在 CTFd_zh\CTFd\views.py 里面找个位置创建这几个字段,这样以来数据库里面就有了 bot 相关的记录了

image.png
image.png

然后对后台前端代码进行修改,通过新增一个 bot.html 来新增一个配置页面
首先是 config.html 中需要增加一个选项

image.png
image.png

下面也要对应 include 进来

image.png
image.png

bot.html 放在 configs 文件夹下面,代码如下:

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
<div role="tabpanel" class="tab-pane config-section active" id="bots">
<form method="POST" autocomplete="off" class="w-100">
<div class="form-group">
<label for="bot-url">
机器人地址<br />
<small class="form-text text-muted">
地址中带好参数名只留消息内容供平台拼接
</small>
<small class="form-text text-muted">
例如:http://127.0.0.1:5700/send_group_msg?group_id=xxxxxx&message=
</small>
</label>
<input class="form-control" id="bot-url" name="boturl" type="text" />
</div>
<div class="form-group">
<label for="bot-text">
消息内容<br />
<small class="form-text text-muted">
格式:恭喜%s做出题目%s,默认第一个参数为用户名,第二个参数为题目名称
</small>
</label>
<input class="form-control" id="bot-text" name="bottext" type="text" />
</div>
<div class="form-group">
<label for="botof"> 是否开启机器人 </label>
<div>
<select class="form-control custom-select" id="botof" name="bot">
<option value="0">关闭</option>
<option value="1">开启</option>
</select>
</div>
</div>
<button type="submit" class="btn btn-md btn-primary float-right">
更新
</button>
</form>
</div>

后台页面就可以对他们进行设置啦

image.png
image.png

出题

参考:
https://www.zhaoj.in/read-6259.html
https://blog.csdn.net/Cypher_X/article/details/115359957
https://www.v0n.top/2020/05/01/如何正确使用 Docker 出一道 CTF 题目/

pwn

这个不错:https://github.com/TaQini/pwn_docker
直接用打包好的镜像,把二进制文件命名为 pwn:
Dockerfile:

1
2
FROM glzjin/pwn_base_16
COPY pwn /pwn/pwn

glzjin/pwn_base_16 代表 Ubuntu 16.04,glzjin/pwn_base_18 代表 Ubuntu 18.04
glzjin/pwn_base_19 代表 Ubuntu 19.04,glzjin/pwn_base_20 代表 Ubuntu 20.04

然后:docker build -t <你在 dockerhub的用户名/你的镜像名> ./
例如:docker build -t yichen115/pwn_testnc ./

web

整体结构:files 是个文件夹中有 flag.sh 和 html 文件夹,html 是题目源码

1
2
3
4
5
6
7
docker-compose.yml
Dockerfile
files--|
|--flag.sh
|--html--|
|--index.php
|--flag.php

首先当然是题目了,就完整的写好题目就可以啦,放在一个 html 文件夹里
flag 自定义一个特殊的后面用,这里使用 flag{dockerflag},后面会自动替换

然后写一个 flag.sh 用来动态生成密码,使用 sed 去 flag.php 查找 flag{dockerflag} 然后替换为 ctfd-whale 自动生成的 $FLAG,最后删掉这个文件,sed 查找的目录根据需要自己写

1
2
3
4
5
sed -i "s/flag{dockerflag}/$FLAG/" /var/www/html/flag.php
export FLAG=not_flag
FLAG=not_flag

rm -f /flag.sh

如果是 sql 注入这类的题,需要写到数据库中

1
2
3
4
5
6
7
8
9
#!/bin/bash

# 修改数据库中的 FLAG,自定义sql语句把
mysql -e "USE ctf;INSERT INTO Flag VALUES('$FLAG');" -uroot -proot

export FLAG=not_flag
FLAG=not_flag

rm -f /flag.sh

然后编写 Dockerfile

1
2
3
4
5
6
7
FROM ctftraining/base_image_nginx_mysql_php_73
#这里自己选择基础镜像

COPY ./files /tmp/
RUN cp -rf /tmp/html /var/www/ \ #把题目复制到 /var/www
&& cp -f /tmp/flag.sh /flag.sh \ #强制拷贝flag.sh到根目录
&& chown -R www-data:www-data /var/www/html \ #设置权限

docker-compose.yml 里面的端口以及 flag 都会随机生成,没必要,倒是可以用来生成镜像
docker-compose up -d

1
2
3
4
5
6
7
8
9
10
11
12
version: "2"

services:

web:
build: .
image: yichen115/web_get_method
restart: always
ports:
- "127.0.0.1:8302:80"
environment:
- FLAG=flag{123}

整理文档的时候突然懵了,我没看到有运行 flag.sh 的地方呀,原来 ctftraining 的镜像带着,docker-php-entrypoint 里面有个

1
2
3
if [[ -f /flag.sh ]]; then
source /flag.sh
fi

其他类型的直接静态 flag 吧

报错汇总

报错:

ERROR: The Compose file ‘./docker-compose.yml’ is invalid because: networks.frp_containers value Additional properties are not allowed (‘attachable’ was unexpected)

解决:将 docker-compose.yml 开头的版本改为 3.3
感谢 @wh1te 师傅提供解决方法