GZCTF平台搭建

环境:

1
2
3
4
Ubantu22.04.1
Docker 24.0.7
docker-compose 1.29.2
GZCTF 0.18

GZCTF官方文档: https://docs.ctf.gzti.me/zh/quick-start

一. 安装Docker和Docker-compose

1
2
sudo apt update
sudo apt install docker.io docker-compose

执行命令后, 检查是否成功安装

1
2
docker -v
docker-compose -v

二. 部署GZCTF

0x01.创建GZCTF部署目录

1
2
3
cd /
mkdir GZCTF
cd GZCTF

0x02. 编辑GZCTF配置文件appsettings.json, docker-compose.yml并保存于 /GZCTF 目录下

appsettings.json:

1
2
3
<Your POSTGRES_PASSWORD>设置为数据库密码
<Your PUBLIC_ENTRY> 需改为外部访问地址 //是你的虚拟机ip
<Your XOR_KEY> 设置为用于加密比赛私钥的随机字符串(任意填写即可)
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
{
"AllowedHosts": "*",
"ConnectionStrings": {
"Database": "Host=db:5432;Database=gzctf;Username=postgres;Password=<Your POSTGRES_PASSWORD>"
},
"EmailConfig": {
"SendMailAddress": "a@a.com",
"UserName": "",
"Password": "",
"Smtp": {
"Host": "localhost",
"Port": 587
}
},
"XorKey": "<Your XOR_KEY>",
"ContainerProvider": {
"Type": "Docker", // or "Kubernetes"
"PortMappingType": "Default", // or "PlatformProxy"
"EnableTrafficCapture": false,
"PublicEntry": "<Your PUBLIC_ENTRY>", // or "xxx.xxx.xxx.xxx"
// optional
"DockerConfig": {
"SwarmMode": false,
"Uri": "unix:///var/run/docker.sock"
}
},
"RequestLogging": false,
"DisableRateLimit": true,
"RegistryConfig": {
"UserName": "",
"Password": "",
"ServerAddress": ""
},
"CaptchaConfig": {
"Provider": "None", // or "CloudflareTurnstile" or "GoogleRecaptcha"
"SiteKey": "<Your SITE_KEY>",
"SecretKey": "<Your SECRET_KEY>",
// optional
"GoogleRecaptcha": {
"VerifyAPIAddress": "https://www.recaptcha.net/recaptcha/api/siteverify",
"RecaptchaThreshold": "0.5"
}
},
"ForwardedOptions": {
"ForwardedHeaders": 5,
"ForwardLimit": 1,
"TrustedNetworks": ["192.168.12.0/8"]
}
}

:

1
2
3
4
5
<Your POSTGRES_PASSWORD> 设置为数据库密码, 与上文json文件的一致即可
<Your GZCTF_ADMIN_PASSWORD> 设置为初始管理员密码, 部署成功后默认管理员账户为admin, 密码为该处值
ports:
- "80:8080"
//这里你需要按照你服务去更改,前者是它把服务映射到你电脑的什么端口,后者是他服务端口,假如你之前在电脑开启过nginx或者apache服务那么主机的80端口可能是被占用的你需要把它配置到其他端口即可比如"1234:8080",然后访问你虚拟机ip:1234即可
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
version: "3.0"
services:
gzctf:
image: registry.cn-shanghai.aliyuncs.com/gztime/gzctf:develop
restart: always
environment:
- "GZCTF_ADMIN_PASSWORD=<Your GZCTF_ADMIN_PASSWORD>"
# choose your backend language `en_US` / `zh_CN` / `ja_JP`
- "LC_ALL=zh_CN.UTF-8"
ports:
- "80:8080"
volumes:
- "./data/files:/app/files"
- "./appsettings.json:/app/appsettings.json:ro"
# - "./kube-config.yaml:/app/kube-config.yaml:ro" # this is required for k8s deployment
- "/var/run/docker.sock:/var/run/docker.sock" # this is required for docker deployment
depends_on:
- db

db:
image: postgres:alpine
restart: always
environment:
- "POSTGRES_PASSWORD=<Your POSTGRES_PASSWORD>"
volumes:
- "./data/db:/var/lib/postgresql/data"

0x03. 启动GZCTF

1
2
#在docker-compose.yml所在目录
docker-compose up -d

但是启动之前需要换源,因为未知原因国内大部分docker镜像站都不可用了

三. docker走本机代理

1
2
3
4
5
vi /etc/docker/daemon.json
{
"registry-mirrors": ["https://hub.docker.com/"]
}
官方源

修改 Docker 代理配置

  1. 修改配置文件(如果没有就创建)

    1
    2
    3
    mkdir /etc/systemd/system/docker.service.d
    touch /etc/systemd/system/docker.service.d/proxy.conf
    vim /etc/systemd/system/docker.service.d/proxy.conf
  1. 按照以下模板修改代理的 IP 和 端口号信息:

    1
    2
    3
    [Service]
    Environment="HTTP_PROXY=http://192.168.31.1:7890"
    Environment="HTTPS_PROXY=http://192.168.31.1:7890"
  2. 加载配置,重启 Docker

    1
    2
    3
    4
    5
    6
    # 加载配置
    systemctl daemon-reload
    # 重启docker
    systemctl restart docker
    # 查看代理配置是否生效
    systemctl show --property=Environment docker

到这里你已经成功了99%接下来

1
2
docker-compose up -d
docker ps

你会看到这两个容器

image-20240909103404999

四. 进入靶场, 登陆管理员

1
2
账户:admin
密码:<Your GZCTF_ADMIN_PASSWORD>设定的值

五. 创建比赛, 编辑题目

0x01. Web动态Flag题目部署

01. 创建如下结构目录(这里只做一个演示)

1
2
3
4
+-- src
| +-- index.php
| +-- flag.sh
+-- Dockerfile

接下来分别讲解每个文件的用处

02. index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
highlight_file(__FILE__);
ini_set('open_basedir', '/var/www/html/');

if(isset($_GET['cmd'])&&$cmd = $_GET['cmd']){
if (!preg_match('/flag|phpinfo|system|exec|passthru|shell_exec|ob_start|echo|popen|proc_open|fopen|popen|base64|\<|\>|{|}|\^|\~|\$|\`|\.|\*|\?|\'|\"/im', $cmd)) {
eval($cmd);
}else{
echo '达咩!!!';
}
}else{
echo '你要毁了网络安全吗?';
}
?>

03. flag.sh

1
2
3
4
5
#!/bin/sh
echo "flag{testflag}" > /flag
sed -i "s/flag{testflag}/$GZCTF_FLAG/" /flag
export GZCTF_FLAG=""
rm -rf /flag.sh

官方文档中提到, 容器采用 $GZCTF_FLAG 环境变量进行注入(通俗易懂的讲, 就是GZCTF会生成一个Flag放在 $GZCTF_FLAG 环境变量中), 所以我们只要把 $GZCTF_FLAG 放到我们想放的位置就可以了

所以就采用如上命令, 把预先留的flag{testflag}替换为 $GZCTF_FLAG 的值

export GZCTF_FLAG=””这一句的用处, 我觉得是, 为了防止从环境变量中得到Flag, 从而绕过了原本题目

踩坑:

当时我是利用windows系统创建的flag.sh文件然后再拖入到ubuntu中的,结果每次flag.sh文件的执行都有各种问题,解决方案就是在ubuntu中创建,然后把内容复制到ubuntu的flag.sh文件中就可以了

04. Dockerfile

1
2
3
4
5
FROM ctftraining/base_image_nginx_php_73

COPY src /var/www/html

RUN mv /var/www/html/flag.sh / && chmod +x /flag.sh

上文选用了 ctftraining/base_image_nginx_php_73 来作为基础镜像, 该镜像可以自动运行flag.sh帮助实现动态Flag, 关于该镜像的更多信息可以通过如下仓库

1
https://github.com/CTFTraining/base_image_nginx_php_73

这里再提一句, GZCTF中, Dockerfile不需要写 EXPOSE 来暴露端口, 直接在Web页面创建题目时填写即可, 如果写了反而会导致占用端口资源等问题

05. 创建镜像

1
2
在Dockerfile所在目录下, 运行
docker build -t name/web1 .

name为dockerhub名(当然如果你不push到DockerHub上这里乱写也行)
testweb为镜像名(自定义)
“.”为版本号,代表latest

1. 查看镜像
1
docker images
3.添加代理

想要push到仓库,我们如果虚拟机是nat模式就需要配置代理

1
2
3
4
5
6
7
sudo su
通过 vim /etc/environment 编辑文件,添加如下内容:
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"

export http_proxy="http://192.168.31.1:7890"
export https_proxy="https://192.168.31.1:7890"
export socks_proxy="socks5://192.168.31.1:7890"

这里的 ip 就是 VMnet8 的 ip,端口是代理的端口

通过 curl www.google.com 测验代理是否成功

2. push镜像
1
2
docker login
docker push name/web1

0x01. 进入GZCTF平台, 部署题目

01. 创建比赛

image-20240909112934053

02. 创建题目时, 选择动态容器

image-20240909113014225

03. 设置容器镜像

image-20240909113302265

容器镜像填入刚刚我们push上去的地址(即上文框出的地址, name/web1),
服务端口根据传入的容器需求填写(即容器对外开放的端口)
剩下的参数自行决定

04. 设置Flag生成规则

image-20240909113434089

点击上文界面的编辑附件及flag

如果没有需求, 直接这样也可以, 有更多需求可以查看官方文档

六. 如果需要连接数据库

由于镜像

1
ctftraining/base_image_nginx_mysql_php_73

内置mysql并且已经和初始化存在一个ctftraining表供我们利用

我们只需要对应添加flag进数据库即可

1
2
3
4
5
6
flag.sh

#!/bin/sh
mysql -e "USE ctftraining; CREATE TABLE IF NOT EXISTS \`FLAG_TABLE\` (\`FLAG_COLUMN\` varchar(100) NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;INSERT INTO \`FLAG_TABLE\` VALUES ('$GZCTF_FLAG');" -uroot -proot
export GZCTF_FLAG=""
rm -rf /flag.sh
1
2
3
4
5
6
7
8
9
10
11
Dockerfile

FROM ctftraining/base_image_nginx_mysql_php_73

COPY src /var/www/html

RUN mv /var/www/html/flag.sh / \
&& chmod +x /flag.sh \
&& chown -R www-data:www-data /var/www/html \
&& sed -i 's/skip-network/#skip-network/' /etc/my.cnf.d/mariadb-server.cnf

七. 常用docker命令

0x01. docker重启容器

1
2
3
4
5
docker restart 名称/ID #重启容器,保留状态和数据。
docker start 名称/ID #重启已停止容器,不保留状态和数据。
docker kill 名称/ID #强制停止容器,然后使用 docker start 重启。
docker-compose restart 名称/ID #重启所有容器(使用 docker compose 管理时)。
重启 #主机以重启所有容器。

0x02. docker删除镜像

1
2
docker rmi 名称/ID #删除已经生成的镜像
docker rmi -f 名称/ID #强制删除镜像

0x03. docker停止并删除容器

1
2
3
4
docker stop 名称/ID #停止容器
docker rm 名称/ID #删除容器
docker stop $(docker ps -aq) #停止所有运行容器
docker rm $(docker ps -aq) #停止所有容器

0x04. docker打包镜像

1
docker save 镜像名 > xxx.tar

0x05. docker解压镜像

1
docker load -i image.tar

0x06. docker启动镜像

1
docker run -d -p 8080:80 -e GZCTF_FLAG=flag{111} image_name

0x07. docker进入到容器内部

1
2
3
4
5
docker exec -it 容器id /bin/sh
退出exit

docker exec -it 容器id /bin/sh
退出ctrl + D

参考文章:

VMware、Docker - 让虚拟机走主机代理,解决镜像封禁问题

2024年7月,解决国内无法拉取docker官方镜像问题

GZCTF靶场从零开始搭建教程(一)>>单机部署

GZCTF靶场从零开始搭建教程(二)>>动态Flag题目部署+本地上传Docker镜像部署