GZCTF平台搭建
GZCTF是一个开源平台,我们可以去github上面下载平台源码
https://github.com/GZTimeWalker/GZCTF
然后官方也给了详细的搭建教程
https://gzctf.gzti.me/zh/guide/start/quick-start
这里我就记录一下搭建过程,供后来的师傅参考
一、安装docker(CentOS)
1.卸载旧版本docker
            
            
              bash
              
              
            
          
          sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine2.安装依赖包
            
            
              bash
              
              
            
          
          sudo yum install -y yum-utils device-mapper-persistent-data lvm23.添加Docker官方仓库
            
            
              bash
              
              
            
          
          sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo4.安装Docker引擎
            
            
              bash
              
              
            
          
          sudo yum install docker-ce docker-ce-cli containerd.io5.启动Docker服务
            
            
              bash
              
              
            
          
          sudo systemctl start docker
sudo systemctl enable docker6.验证安装
            
            
              bash
              
              
            
          
          sudo docker --version
sudo docker run hello-world
出现图中所示version即代表docker配置成功
7.配置国内源(镜像加速)
            
            
              bash
              
              
            
          
          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信息,验证是否成功换源
            
            
              bash
              
              
            
          
          docker info
至此,我们就完成了docker的搭建
二、搭建平台
前往官网下载源码(release)
解压后修改目录名,放在服务器的目录中

然后针对我们自己的需要修改配置文件。
appsettings.json修改如下
            
            
              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修改如下
            
            
              dockerfile
              
              
            
          
          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"然后就可以在当前目录中
            
            
              bash
              
              
            
          
          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端口占用的程序,把他停掉
            
            
              bash
              
              
            
          
          netstat -tunlp | grep :80
可以看到httpd正在占用80端口,把他停掉
            
            
              bash
              
              
            
          
          systemctl stop httpd重新查看端口情况

80端口占用情况已解决,重新compose up即可。
2.更换平台搭建端口
修改docker-compose.yml文件中
            
            
              dockerfile
              
              
            
          
              ports:
      - "80:8080"将80改为其他未占用端口,重新compose up,搭建完成后访问对应端口即可。
搭建成功示例


顺便吐槽一句,GZCTF真的比A1CTF好搭多了,A1虽然很帅,但是搭建教程几乎为零,配置环境和文件也很麻烦,而GZCTF只需要一个docker就够了。
动态靶机&动态flag
作为一名合格的web手,出题肯定是要出动态环境的。作为一名初探出题的小萌新,我也是刚刚学会了如何实现动态靶机和动态flag。这里也是记录一下,一方面防止自己忘了,另一方面为后继web出题的师傅提供一个有力可参考的教程。
首先我们要明白docker容器和镜像的含义和区别
docker容器&docker镜像
Docker 镜像
作用
- 只读的模板
 Docker 镜像是一个静态的、只读的文件包。它包含了运行某个软件所需的一切:代码、运行时环境、系统工具、系统库和设置。你可以把它想象成一个面向对象的"类"(Class),或者一个安装程序的".iso"文件。
- 创建容器的基础
 镜像的唯一目的就是用于创建容器。一个镜像可以创建出多个相互独立、互不干扰的容器实例。
- 分层结构与共享
 镜像采用联合文件系统(UnionFS) ,由一系列只读层组成。每一层代表 Dockerfile 中的一条指令。这种分层结构带来了巨大优势:- 共享性:不同的镜像可以共享相同的基础层(例如,Ubuntu 基础层)。当你拉取一个基于 Ubuntu 的新镜像时,如果你的系统里已经有 Ubuntu 层,就无需重复下载,节省了磁盘空间和网络带宽。
- 高效性:构建新镜像时,只需添加或修改变化的层,而不需要重建整个文件系统。
 
- 版本控制与分发
 镜像可以被版本化、存储和分发。你可以使用docker commit来创建新镜像,也可以用docker push将镜像上传到镜像仓库(如 Docker Hub),其他人可以通过docker pull下载并使用。这保证了环境的一致性------在任何地方运行的同一个镜像,其内部内容都是完全相同的。
简单比喻:
Docker 镜像就像是房屋的蓝图(Blueprint)或者软件的安装光盘。 蓝图本身不能住人,但它包含了建造房屋所需的所有信息和规格。
Docker 容器
作用
- 
镜像的运行实例 容器是镜像的一个动态的、可运行的实例 。当你执行 docker run命令时,Docker 会从镜像创建一个容器。继续上面的比喻,如果镜像是蓝图,那么容器就是根据蓝图建造出来的、可以实际入住的房子。
- 
隔离的进程 一个容器代表一个独立的、轻量级的运行时环境。它包含: - 一个独立的进程空间:容器内通常运行一个主进程。
- 一个独立的文件系统:基于镜像提供,但额外有一个可写的薄层。
- 一个独立的网络配置:拥有自己的 IP 地址、端口映射等。
- 一个独立的资源限制:可以限制其 CPU、内存的使用。
 这些隔离性是通过 Linux 的命名空间(Namespaces)和控制组(Cgroups)技术实现的。 
- 
可写层 当容器启动时,Docker 会在镜像的只读层之上添加一个薄薄的可写层。所有对运行中容器的修改(如创建新文件、修改现有文件、安装新软件)都发生在这个可写层中。这使得容器变得"动态"。 
- 
应用的生命周期 容器是应用真正运行的地方。你可以启动、停止、重启或删除容器。它的状态是瞬时的(ephemeral),默认情况下,当容器被删除时,其可写层中的数据也会一并丢失。 
简单比喻:
Docker 容器就是根据蓝图(镜像)建造并正在运行的房子(Running Instance)。 你可以在房子里活动(运行应用),添置家具(修改文件),但房子的基本结构(镜像)是不变的。
核心区别与关系总结
| 特性 | Docker 镜像 | Docker 容器 | 
|---|---|---|
| 本质 | 静态的、只读的模板 | 动态的、可运行的实例 | 
| 状态 | 不可变 | 可变(通过可写层) | 
| 存储 | 一系列只读的层 | 镜像的只读层 + 一个可写层 | 
| 创建方式 | 通过 Dockerfile使用docker build构建 | 通过 docker run从镜像创建 | 
| 生命周期 | 无状态,除非被更新或删除 | 有状态,可以被启动、停止、重启、删除 | 
| 数量关系 | 一个镜像可以创建多个容器 | 一个容器基于一个镜像 | 
| 类比 | 软件的安装程序(.exe/.iso) 或 房屋的蓝图 | 正在运行的软件进程 或 建好并入住的房子 | 
它们之间的关系流程
- 构建镜像 :开发者编写 Dockerfile,使用docker build命令构建出一个镜像。这个镜像被存储在本地或推送到远程仓库。
- 运行容器 :用户或运维人员使用 docker run命令,指定一个镜像来创建并启动一个容器。
- 容器运行:在容器运行期间,所有数据修改都发生在容器自己的可写层。
- 持久化数据:如果需要数据持久化,可以使用 Docker 卷(Volumes)或绑定挂载(Bind Mounts),将数据存储在宿主机上,而不是容器的可写层。
- 停止与删除:当容器完成任务后,可以被停止和删除。删除容器时,其可写层也会被清除,但底层的镜像保持不变,随时可以用来创建新的、干净的容器。
所以我们出题的思路:配置题目环境,制作为镜像文件,放到平台制作容器,完毕。
看起来很简单是不是,其实也是比较简单的,只不过之前捋不清什么是容器,什么是镜像,分别有什么作用。当我们理解了他们,思路就清晰了。下面我们就根据这个思路,一步一步实现动态靶机。
配置题目环境
这里给大家推荐一个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
具体使用也很简单,我们创建一个个人实例,选择本地仓库(划重点),然后创建镜像仓库,仓库类型设为公开(划重点!!否则容器无法创建)

进入镜像仓库管理,根据操作指南进行操作即可。这里选取我们需要用到的几条命令进行说明
            
            
              bash
              
              
            
          
          从Registry中拉取镜像
docker pull 你的镜像仓库地址:[镜像版本号]
            
            
              bash
              
              
            
          
          将镜像推送到Registry
docker login --username=你的阿里云镜像仓库账户 你的镜像仓库链接
docker tag [ImageId] 你的镜像仓库地址:[镜像版本号]
docker push 你的镜像仓库地址:[镜像版本号]我们需要做的,就是在本地制作镜像,然后push到镜像仓库中。
制作本地镜像
在dockerfile目录中,执行
            
            
              bash
              
              
            
          
          docker build -t 镜像名 .
等待镜像创建完成,我们执行
            
            
              bash
              
              
            
          
          docker images
找到刚才制作的镜像id,执行
            
            
              bash
              
              
            
          
          docker login --username=你的阿里云镜像仓库账户 你的镜像仓库链接
docker tag [ImageId] 你的镜像仓库地址:[镜像版本号]
docker push 你的镜像仓库地址:[镜像版本号]
刷新镜像仓库页面,就可以看到镜像已经成功上传了

实现动态靶机
到目前为止题目镜像已经全部配置完毕,我们可以开始上题了
来到题目管理,新增题目,选择动态靶机,在容器镜像一栏中填写
镜像地址:版本号
点击创建测试容器

如果你也像图中一样显示实例已创建,那么恭喜你!你已经完全学会如何搭建一个CTF平台,并在平台上部署动态靶机了!
无法创建测试容器
当我们在容器镜像一栏中填写远程镜像仓库地址时,此时点击创建测试容器,可能会出现弹窗:服务器内部错误,无法创建容器。经过测试,想要解决这个问题也很简单:
1.在搭建平台的服务器上创建镜像,直接使用本地镜像
2.在搭建平台的服务器上把镜像仓库中的镜像pull到本地使用
可能会有朋友觉得第二种方法多此一举:在本地创建镜像,push到远程仓库,然后再pull到本地?
其实有的师傅的服务器可能会出现无法在本地创建镜像的问题,就比如我们学校的服务器,执行docker build -t name .就是会报错,就是无法创建镜像。到处搜、到处找办法解决也没招,最后只能在我自己的服务器上创建镜像,push到我的远程仓库,然后再在学校服务器上把我远程仓库的镜像pull下来,算是一个中转吧。
具体原因不清楚,至少能用了