手把手教你GZCTF平台搭建+动态靶机部署,超详细教程,看这一篇就够了

GZCTF平台搭建

GZCTF是一个开源平台,我们可以去github上面下载平台源码

https://github.com/GZTimeWalker/GZCTF

然后官方也给了详细的搭建教程

https://gzctf.gzti.me/zh/guide/start/quick-start

这里我就记录一下搭建过程,供后来的师傅参考

一、安装docker(CentOS)

1.卸载旧版本docker

 sudo yum remove docker \
                   docker-client \
                   docker-client-latest \
                   docker-common \
                   docker-latest \
                   docker-latest-logrotate \
                   docker-logrotate \
                   docker-engine

2.安装依赖包

 sudo yum install -y yum-utils device-mapper-persistent-data lvm2

3.添加Docker官方仓库

 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

4.安装Docker引擎

 sudo yum install docker-ce docker-ce-cli containerd.io

5.启动Docker服务

 sudo systemctl start docker
 sudo systemctl enable docker

6.验证安装

 sudo docker --version
 sudo docker run hello-world

出现图中所示version即代表docker配置成功

7.配置国内源(镜像加速)

 sudo mkdir -p /etc/docker
 sudo tee /etc/docker/daemon.json <<-'EOF'
 {
   "registry-mirrors": [
     "https://docker.mirrors.ustc.edu.cn",
     "https://hub-mirror.c.163.com"
   ]
 }
 EOF
 sudo systemctl daemon-reload
 sudo systemctl restart docker

换源后我们查看docker信息,验证是否成功换源

 docker info

至此,我们就完成了docker的搭建

二、搭建平台

前往官网下载源码(release)

解压后修改目录名,放在服务器的目录中

然后针对我们自己的需要修改配置文件。

appsettings.json修改如下

 {
   "AllowedHosts": "*",
   "ConnectionStrings": {
     "Database": "Host=db:5432;Database=gzctf;Username=postgres;Password=Admin1234." //Password是你自己的数据库密码
   },
   "EmailConfig": {
     "SendMailAddress": "a@a.com",
     "UserName": "",
     "Password": "",
     "Smtp": {
       "Host": "localhost",
       "Port": 587
     }
   },
   "XorKey": "Admin1234.", //同样的,你自己的密码
   "ContainerProvider": {
     "Type": "Docker", // or "Kubernetes"
     "PortMappingType": "Default", // or "PlatformProxy"
     "EnableTrafficCapture": false,
     "PublicEntry": "你自己的服务器ip", // 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"]
   }
 }

docker-compose.yml修改如下

 version: "3.0"
 services:
   gzctf:
     image: gztime/gzctf:latest #gzctf镜像源
     restart: always
     environment:
       - "GZCTF_ADMIN_PASSWORD=Admin1234." #平台管理员账户密码,修改为你自己的
       # 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 #postgres镜像源
     restart: always
     environment:
       - "POSTGRES_PASSWORD=Admin1234." #数据库密码
     volumes:
       - "./data/db:/var/lib/postgresql/data"

然后就可以在当前目录中

 docker compose up -d

启动GZCTF,随后我们就可以通过80端口访问平台了。

可能遇到的问题

1.docker compose up时网络超时

大概率是gzctf或者postgres的镜像源访问不到,导致网络超时,更换其他镜像源加速即可。

2.端口占用

报错如下:

Error response from daemon: driver failed programming external connectivity on endpoint gzctf-gzctf-1 (d22fc1ae61f51d4aa3265a4e0060bb4244b2ca9861d263proxy: listen tcp4 0.0.0.0:80: bind: address already in use

这里可以看到我们的80端口被占用了。两种解决方法

1.查找占用80端口占用的程序,把他停掉
 netstat -tunlp | grep :80

可以看到httpd正在占用80端口,把他停掉

 systemctl stop httpd

重新查看端口情况

80端口占用情况已解决,重新compose up即可。

2.更换平台搭建端口

修改docker-compose.yml文件中

     ports:
       - "80:8080"

将80改为其他未占用端口,重新compose up,搭建完成后访问对应端口即可。

搭建成功示例

顺便吐槽一句,GZCTF真的比A1CTF好搭多了,A1虽然很帅,但是搭建教程几乎为零,配置环境和文件也很麻烦,而GZCTF只需要一个docker就够了。

动态靶机&动态flag

作为一名合格的web手,出题肯定是要出动态环境的。作为一名初探出题的小萌新,我也是刚刚学会了如何实现动态靶机和动态flag。这里也是记录一下,一方面防止自己忘了,另一方面为后继web出题的师傅提供一个有力可参考的教程。

首先我们要明白docker容器和镜像的含义和区别

docker容器&docker镜像

Docker 镜像

作用
  1. 只读的模板 Docker 镜像是一个静态的、只读的文件包。它包含了运行某个软件所需的一切:代码、运行时环境、系统工具、系统库和设置。你可以把它想象成一个面向对象的“类”(Class),或者一个安装程序的“.iso”文件。

  2. 创建容器的基础 镜像的唯一目的就是用于创建容器。一个镜像可以创建出多个相互独立、互不干扰的容器实例。

  3. 分层结构与共享 镜像采用联合文件系统(UnionFS),由一系列只读层组成。每一层代表 Dockerfile 中的一条指令。这种分层结构带来了巨大优势:

    • 共享性:不同的镜像可以共享相同的基础层(例如,Ubuntu 基础层)。当你拉取一个基于 Ubuntu 的新镜像时,如果你的系统里已经有 Ubuntu 层,就无需重复下载,节省了磁盘空间和网络带宽。

    • 高效性:构建新镜像时,只需添加或修改变化的层,而不需要重建整个文件系统。

  4. 版本控制与分发 镜像可以被版本化、存储和分发。你可以使用 docker commit 来创建新镜像,也可以用 docker push 将镜像上传到镜像仓库(如 Docker Hub),其他人可以通过 docker pull 下载并使用。这保证了环境的一致性——在任何地方运行的同一个镜像,其内部内容都是完全相同的。

简单比喻: Docker 镜像就像是房屋的蓝图(Blueprint)或者软件的安装光盘。 蓝图本身不能住人,但它包含了建造房屋所需的所有信息和规格。


Docker 容器

作用
  1. 镜像的运行实例 容器是镜像的一个动态的、可运行的实例。当你执行 docker run 命令时,Docker 会从镜像创建一个容器。继续上面的比喻,如果镜像是蓝图,那么容器就是根据蓝图建造出来的、可以实际入住的房子

  2. 隔离的进程 一个容器代表一个独立的、轻量级的运行时环境。它包含:

    • 一个独立的进程空间:容器内通常运行一个主进程。

    • 一个独立的文件系统:基于镜像提供,但额外有一个可写的薄层。

    • 一个独立的网络配置:拥有自己的 IP 地址、端口映射等。

    • 一个独立的资源限制:可以限制其 CPU、内存的使用。

    这些隔离性是通过 Linux 的命名空间(Namespaces)和控制组(Cgroups)技术实现的。

  3. 可写层 当容器启动时,Docker 会在镜像的只读层之上添加一个薄薄的可写层。所有对运行中容器的修改(如创建新文件、修改现有文件、安装新软件)都发生在这个可写层中。这使得容器变得“动态”。

  4. 应用的生命周期 容器是应用真正运行的地方。你可以启动、停止、重启或删除容器。它的状态是瞬时的(ephemeral),默认情况下,当容器被删除时,其可写层中的数据也会一并丢失。

简单比喻: Docker 容器就是根据蓝图(镜像)建造并正在运行的房子(Running Instance)。 你可以在房子里活动(运行应用),添置家具(修改文件),但房子的基本结构(镜像)是不变的。

核心区别与关系总结

特性Docker 镜像Docker 容器
本质静态的、只读的模板动态的、可运行的实例
状态不可变可变(通过可写层)
存储一系列只读的层镜像的只读层 + 一个可写层
创建方式通过 Dockerfile 使用 docker build 构建通过 docker run 从镜像创建
生命周期无状态,除非被更新或删除有状态,可以被启动、停止、重启、删除
数量关系一个镜像可以创建多个容器一个容器基于一个镜像
类比软件的安装程序(.exe/.iso)房屋的蓝图正在运行的软件进程建好并入住的房子

它们之间的关系流程

  1. 构建镜像:开发者编写 Dockerfile,使用 docker build 命令构建出一个镜像。这个镜像被存储在本地或推送到远程仓库。

  2. 运行容器:用户或运维人员使用 docker run 命令,指定一个镜像来创建并启动一个容器。

  3. 容器运行:在容器运行期间,所有数据修改都发生在容器自己的可写层。

  4. 持久化数据:如果需要数据持久化,可以使用 Docker 卷(Volumes)或绑定挂载(Bind Mounts),将数据存储在宿主机上,而不是容器的可写层。

  5. 停止与删除:当容器完成任务后,可以被停止和删除。删除容器时,其可写层也会被清除,但底层的镜像保持不变,随时可以用来创建新的、干净的容器。

所以我们出题的思路:配置题目环境,制作为镜像文件,放到平台制作容器,完毕。

看起来很简单是不是,其实也是比较简单的,只不过之前捋不清什么是容器,什么是镜像,分别有什么作用。当我们理解了他们,思路就清晰了。下面我们就根据这个思路,一步一步实现动态靶机。

配置题目环境

这里给大家推荐一个github上面的项目,存放着各种docker模板,可以根据需要自行修改使用

https://github.com/CTF-Archives/ctf-docker-template

我们以web-nginx-php73为例,看一下模板中各文件到底实现了什么功能

config/nginx.conf

这个文件放在哪、叫什么名字都没有关系,只要后缀是conf,内容符合环境需要即可。在dockerfile中会手动引用。(比如在另一道题目中这个文件就叫做default.conf)

 # daemon off;
 ​
 worker_processes  auto;
 ​
 events { # 定义每个工作进程可以处理的最大并发连接数为1024
     worker_connections  1024;
 }
 ​
 http { # 基础http设置
     include       /etc/nginx/mime.types; # 引入MIME类型定义文件
     default_type  application/octet-stream; # 默认Content-Type
     sendfile        on; # 启用高效文件传输
     keepalive_timeout  65; # 保持连接超时时间65秒
 ​
     server { # 虚拟主机配置
         listen       80; # 监听80端口
         server_name  localhost; # 服务器名:localhost
         root         /var/www/html; # 网站根目录:/var/www/html
         index index.php index.html index.htm; # 默认索引文件顺序:先找php,再找html文件
 ​
         location / { # 根路径处理
             try_files $uri  $uri/ /index.php?$args;
         }
 ​
         location ~ \.php$ { # php处理
             try_files $uri =404;
             fastcgi_pass   127.0.0.1:9000; # 将php请求转发给PHP-FPM处理(监听在9000端口)
             fastcgi_index  index.php;
             include        fastcgi_params;
             fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name; # 告诉PHP-FPM要执行的脚本完整路径
         }
 ​
     }
 }
 ​
 #整体工作流程
 ​
 #    用户访问 http://localhost/about.php
 #    Nginx 接收请求,匹配到 PHP location
 #    Nginx 将请求转发给 127.0.0.1:9000 的 PHP-FPM
 #    PHP-FPM 执行 /var/www/html/about.php
 #    PHP-FPM 返回执行结果给 Nginx
 #    Nginx 将结果返回给用户

service/docker-entrypoint.sh

同样的,这个文件的路径、名称均非固定,只要后缀是sh,内容符合环境需要即可。在另一道题目中,这个文件和init.sh作用相似。

 #!/bin/sh
 ​
 rm -f /docker-entrypoint.sh # 删除自身脚本,防止选手通过查看入口点脚本获取解题线索
 ​
 # Get the user
 user=$(ls /home)
 ​
 # Check the environment variables for the flag and assign to INSERT_FLAG
 # 需要注意,以下语句会将FLAG相关传递变量进行覆盖,如果需要,请注意修改相关操作
 if [ "$DASFLAG" ]; then
     INSERT_FLAG="$DASFLAG"
     export DASFLAG=no_FLAG
     DASFLAG=no_FLAG
 elif [ "$FLAG" ]; then
     INSERT_FLAG="$FLAG"
     export FLAG=no_FLAG
     FLAG=no_FLAG
 elif [ "$GZCTF_FLAG" ]; then
     INSERT_FLAG="$GZCTF_FLAG"
     export GZCTF_FLAG=no_FLAG
     GZCTF_FLAG=no_FLAG
 else
     INSERT_FLAG="flag{TEST_Dynamic_FLAG}"
 fi # 检查多种常见的CTF平台环境变量名,将动态传入的flag保存到变量中,然后立即清空环境变量,防止通过环境变量泄露flag。如果没有传入flag,使用测试flag
 ​
 # 将FLAG写入文件 请根据需要修改
 echo $INSERT_FLAG | tee /flag
 ​
 chmod 744 /flag
 ​
 php-fpm & nginx & # 启动PHP-FPM和Nginx服务(在后台运行)
 ​
 echo "Running..."
 ​
 tail -F /var/log/nginx/access.log /var/log/nginx/error.log # 日志监控,在前台持续输出Nginx日志,保持容器运行

Dockerfile

这个名字通常不变

 FROM php:7.3-fpm-alpine
 ​
 # 制作者信息
 LABEL auther_template="CTF-Archives"
 ​
 # 安装必要的软件包
 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories  &&\
     apk add --update --no-cache nginx bash # 这里的repositories也可手动更换
 ​
 # 拷贝容器入口点脚本
 # 可用COPY,可用ADD
 # 这里就是前面说文件路径和名称不固定的原因,路径和名称在这里保持一致即可。
 COPY ./service/docker-entrypoint.sh /docker-entrypoint.sh
 RUN chmod +x /docker-entrypoint.sh
 ​
 # 复制nginx配置文件
 COPY ./config/nginx.conf /etc/nginx/nginx.conf
 ​
 # 复制web项目源码
 COPY src /var/www/html
 ​
 # 重新设置源码路径的用户所有权
 RUN chown -R www-data:www-data /var/www/html
 ​
 # 设置shell的工作目录
 WORKDIR /var/www/html
 ​
 EXPOSE 80
 ​
 # 设置nginx日志保存目录
 VOLUME ["/var/log/nginx"]
 ​
 # 设置容器入口点
 ENTRYPOINT [ "/docker-entrypoint.sh" ]

docker/docker-compose.yaml

这个文件在出题时通常是用不到的,他一般用于多容器协同,具体作用我也不太清楚,待我再沉淀沉淀

src/

存放题目源码的位置

综上,在这些配置文件中我们需要修改的文件很少,基本只要修改题目源码即可。

当我们修改好源码,出好签到题之后(最好真的是签到题),就可以制作镜像文件了。

在此之前,我们需要一个自己的镜像仓库来存放镜像文件。

配置镜像仓库

市面上普遍推荐dockerhub,但是我换了各种源依旧ping不通,也无法login,无奈只好换国内镜像仓库。

因为我的服务器是阿里云的,所以我这里推荐阿里云镜像仓库,

https://cr.console.aliyun.com/cn-hangzhou/instances

具体使用也很简单,我们创建一个个人实例,选择本地仓库(划重点),然后创建镜像仓库,仓库类型设为公开(划重点!!否则容器无法创建)

进入镜像仓库管理,根据操作指南进行操作即可。这里选取我们需要用到的几条命令进行说明

 从Registry中拉取镜像
 docker pull 你的镜像仓库地址:[镜像版本号]

 将镜像推送到Registry
 docker login --username=你的阿里云镜像仓库账户 你的镜像仓库链接
 docker tag [ImageId] 你的镜像仓库地址:[镜像版本号]
 docker push 你的镜像仓库地址:[镜像版本号]

我们需要做的,就是在本地制作镜像,然后push到镜像仓库中。

制作本地镜像

在dockerfile目录中,执行

 docker build -t 镜像名 .

等待镜像创建完成,我们执行

 docker images

找到刚才制作的镜像id,执行

 docker login --username=你的阿里云镜像仓库账户 你的镜像仓库链接
 docker tag [ImageId] 你的镜像仓库地址:[镜像版本号]
 docker push 你的镜像仓库地址:[镜像版本号]

刷新镜像仓库页面,就可以看到镜像已经成功上传了

实现动态靶机

到目前为止题目镜像已经全部配置完毕,我们可以开始上题了

来到题目管理,新增题目,选择动态靶机,在容器镜像一栏中填写

 镜像地址:版本号

点击创建测试容器

如果你也像图中一样显示实例已创建,那么恭喜你!你已经完全学会如何搭建一个CTF平台,并在平台上部署动态靶机了!

无法创建测试容器

当我们在容器镜像一栏中填写远程镜像仓库地址时,此时点击创建测试容器,可能会出现弹窗:服务器内部错误,无法创建容器。经过测试,想要解决这个问题也很简单:

1.在搭建平台的服务器上创建镜像,直接使用本地镜像
2.在搭建平台的服务器上把镜像仓库中的镜像pull到本地使用

可能会有朋友觉得第二种方法多此一举:在本地创建镜像,push到远程仓库,然后再pull到本地?

其实有的师傅的服务器可能会出现无法在本地创建镜像的问题,就比如我们学校的服务器,执行docker build -t name .就是会报错,就是无法创建镜像。到处搜、到处找办法解决也没招,最后只能在我自己的服务器上创建镜像,push到我的远程仓库,然后再在学校服务器上把我远程仓库的镜像pull下来,算是一个中转吧。

具体原因不清楚,至少能用了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值