GZCTF的搭建和CTF题目的出题
本文最后更新于28 天前,其中的信息可能已经过时,如有错误请发送邮件到1416359402@qq.com

写的如果不好见谅,作者水平有限

项目地址

GitHub – GZTimeWalker/GZCTF: The GZ::CTF project, an open source CTF platform. · GitHub

平台配置手册 地址:快速上手 – GZ::CTF

GZCTF是docker搭建的

下载docker

curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
systemctl enable docker
systemctl start docker

创建 GZCTF 目录及配置文件和邮箱验证

可以不用邮箱

我用的是QQ邮箱的STMP服务 可以自己开启就行了 不建议使用SMTP,可以用腾讯云或者阿里云服务器里面的邮件推送服务,反正QQ那个SMTP就前面几次可以发送成功后面直接就不行了

mkdir -p /opt/gzctf
cd /opt/gzctf

创建 appsettings.json 文件

在终端输入 nano appsettings.json 我开的有邮箱验证用的QQ的

{
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "Database": "Host=db:5432;Database=gzctf;Username=postgres;Password=<你的数据库密码>"
  },
  "EmailConfig": {
    "SenderAddress": "<发件人邮箱地址>",
    "SenderName": "<发件人显示名称>",
    "UserName": "<SMTP用户名>",
    "Password": "<SMTP授权码/密码>",
    "Smtp": {
      "Host": "<SMTP服务器地址>",
      "Port": 465
    }
  },
  "XorKey": "<用于加密题目私钥的随机字符串>",
  "ContainerProvider": {
    "Type": "Docker", // 容器后端类型:可选 Docker 或 Kubernetes
    "PortMappingType": "Default", // 端口映射模式:可选 Default 或 PlatformProxy
    "EnableTrafficCapture": false, // 是否启用流量抓取
    "PublicEntry": "<服务器公网IP或域名>", // 选手访问题目容器的入口地址
    "DockerConfig": {
      "Uri": "unix:///var/run/docker.sock" // Docker 守护进程连接路径
    }
  },
  "CaptchaConfig": {
    "Provider": "None", // 验证码类型:可选 None, CloudflareTurnstile 或 HashPow
    "SiteKey": "<验证码 SiteKey>",
    "SecretKey": "<验证码 SecretKey>"
  },
  "ForwardedOptions": {
    "ForwardedHeaders": 7,
    "ForwardLimit": 1,
    "KnownIPNetworks": ["192.168.12.0/8"] // 信任的反向代理内网段
  }
}

保存并退出:按 Ctrl+O回车保存,然后按 Ctrl+X退出

创建 nano compose.yml

services:
  gzctf:
    image: registry.cn-shanghai.aliyuncs.com/gztime/gzctf:develop
    restart: always
    environment:
      - "GZCTF_ADMIN_PASSWORD=<你的初始管理员密码>" # 数据库未初始化时的首个管理员密码
      - "LC_ALL=zh_CN.UTF-8" # 平台语言设置,默认为中文
    ports:
      - "80:8080" # 将容器 8080 端口映射到宿主机 80 端口
    volumes:
      - "./data/files:/app/files" # 题目附件及静态文件存储目录
      - "./appsettings.json:/app/appsettings.json:ro" # 挂载配置文件(只读)
      - "/var/run/docker.sock:/var/run/docker.sock" # 允许平台控制宿主机 Docker 环境
    depends_on:
      - db # 确保数据库服务优先启动

  db:
    image: postgres:alpine
    restart: always
    environment:
      - "POSTGRES_PASSWORD=<你的数据库密码>" # 须与 appsettings.json 中的密码保持一致
    volumes:
      - "./data/db:/var/lib/postgresql" # 数据库数据持久化目录

配置好了启动就行

docker compose up -d

有问题阿里云镜像加速

mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://docker.1panel.live",
    "https://docker.m.daocloud.io",
    "https://dockerhub.icu",
    "https://registry.cn-hangzhou.aliyuncs.com"
  ]
}
EOF
systemctl daemon-reload
systemctl restart docker

需要开启系统的网络转发功能

sudo sysctl -w net.ipv4.ip_forward=1

强制防火墙放行 Docker 的流量

sudo iptables -P FORWARD ACCEPT

成功 访问

然后用设置的管理员登录就行

开启邮箱验证 就可以发邮箱验证了

不建议用STMP 不知道什么情况一直出现问题 后面我就禁用邮件验证了

验证码

个人用不到

可以用cloudflare 官网Cloudflare Dashboard | Manage Your Account

修改appsettings

"CaptchaConfig": {
    "Provider": "CloudflareTurnstile",
    "SiteKey": "key",
    "SecretKey": "key",

Web出题和动态 flag

GZCTF动态flag 变量是GZCTF_FLAG

动态 flag 的核心逻辑是:平台在启动题目容器时,会将 flag 注入到环境变量 GZCTF_FLAG 中。 写一个启动脚本(flag.sh),在容器启动的一瞬间,把这个环境变量里的值写到文件里或者数据库里就行

随便写一下题目

目录结构

/opt/ctf/
├── Dockerfile
├── flag.sh
└── src/
    └── index.php

index.php (题目源码)

这是题目页面。出题逻辑是:在页面源码中预留一个占位符,启动容器时由脚本动态替换

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Web 签到</title>
</head>
<body>
    <h1>Welcome to GZCTF</h1>
    <p>这是一道签到题,你的 flag 是:</p>
    <code>{{FLAG}}</code>
</body>
</html>

flag.sh (启动脚本)

这个脚本负责“动态注入”。GZCTF 分配 flag 时会写入 $GZCTF_FLAG 环境变量。

#!/bin/sh

# 1. 接收动态变量,若平台未下发则使用默认值 flag{sanjiu}
export FLAG=${GZCTF_FLAG:-flag{sanjiu}}

# 2. 将源码中的占位符替换为真实 Flag
sed -i "s/{{FLAG}}/$FLAG/g" /var/www/html/index.php

# 3. 彻底销毁环境变量,防止选手通过 /proc/1/environ 或 phpinfo() 提权/非预期读取
unset GZCTF_FLAG
unset FLAG

# 4. 启动 Apache 服务并接管主进程
exec apache2-foreground

Dockerfile

这里使用了阿里云镜像源。

# 基础镜像使用官方 PHP-Apache
FROM php:7.4-apache

# 替换 Debian 软件源为阿里云源,加速后续可能需要的扩展安装
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && 
    sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list

# 拷贝 Web 源码到 Apache 默认目录
COPY src/ /var/www/html/

# 拷贝并配置启动脚本
COPY flag.sh /flag.sh
RUN chmod +x /flag.sh

# 暴露 80 端口供平台映射
EXPOSE 80

# 指定容器启动命令
CMD ["/flag.sh"]

构建镜像

docker build -t web .

有问题改一下全局配置

配置多个国内目前还存活的加速源

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://docker.1panel.live",
    "https://docker.m.daocloud.io",
    "https://docker.nyist.machengqiang.com"
  ]
}
EOF

重启 Docker 服务让配置生效

sudo systemctl daemon-reload
sudo systemctl restart docker

创建题目动态flag

我暴露的是80端口直接用80端口就行

设置

flag{[GUID]} #不一定是flag 也可以是 CTF{[GUID]}

你就会发现是动态flag了

Pwn的出题和动态flag

简单编写一个简单的 C 程序(执行 /bin/sh),并使用 socat 将其绑定到 80 端口上来实现。

目录结构

pwn/
├── Dockerfile
├── flag.sh
└── src/
    └── pwn.c

pwn.c

简单的 pwn 签到源码,作用是直接弹出一个 shell。注意:pwn 题必须关闭 I/O 缓冲,否则通过网络连接时会无法正常输入输出。

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 关闭 I/O 缓冲(防止网络截断)
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    puts("Welcome to GZCTF Pwn Signin!");
    puts("简单的演示!嘻嘻嘻");

    // 直接给予 shell
    system("/bin/sh");

    return 0;
}

flag.sh (启动与动态 flag 脚本)

负责将 GZCTF 下发的变量写入根目录,随后清空变量并启动端口监听。

#!/bin/sh

# 1. 接收动态变量,若平台未下发则使用默认值 flag{sanjiu}
echo "${GZCTF_FLAG:-flag{sanjiu}}" > /flag

# 2. 修改 flag 文件权限为全局只读,防止被选手意外修改或删除
chmod 444 /flag

# 3. 彻底销毁环境变量,防止选手直接运行 env 命令非预期拿到 flag
unset GZCTF_FLAG

# 4. 切换到低权限用户 ctf,使用 socat 监听 80 端口并将流重定向到 pwn 二进制文件
# EXEC 模式下,每次有 nc 连接进来,都会 fork 一个新的 pwn 进程
exec su ctf -c "socat TCP-LISTEN:80,reuseaddr,fork EXEC:/home/ctf/pwn,stderr"

Dockerfile

基于 Ubuntu 22.04,内置阿里云 APT 镜像加速。

# Pwn 题通常使用 Ubuntu 作为基础运行环境
FROM ubuntu:22.04

# 替换 Ubuntu 软件源为阿里云源(加速构建,解决国内 apt update 卡死问题)
RUN sed -i 's@//.*archive.ubuntu.com@//mirrors.aliyun.com@g' /etc/apt/sources.list && 
    sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list

# 安装 gcc (用于编译源码) 和 socat (用于端口转发)
RUN apt-get update && 
    DEBIAN_FRONTEND=noninteractive apt-get install -y gcc socat && 
    rm -rf /var/lib/apt/lists/*

# 创建低权限用户 ctf (Pwn 题绝不能给 root 权限运行二进制,否则会被打穿宿主机)
RUN useradd -m ctf

# 将源码和启动脚本拷贝进容器
COPY src/pwn.c /home/ctf/pwn.c
COPY flag.sh /flag.sh

# 编译 C 源码,并设置权限控制
RUN gcc /home/ctf/pwn.c -o /home/ctf/pwn && 
    chown root:ctf /home/ctf/pwn && 
    chmod 750 /home/ctf/pwn && 
    chmod +x /flag.sh

# 声明暴露 80 端口
EXPOSE 80

# 容器启动时执行的脚本
CMD ["/flag.sh"]

构建镜像

docker build -t pwn .

和前面一样设置

flag{[GUID]}

我还是用的是80端口

启动nc连接就行

docker 命令

1. 镜像构建与管理 
# 构建镜像 
docker build -t <image_name> .
# 列出本地镜像 
docker images
docker image ls
# 搜索镜像
docker search <image_name>
# 从仓库拉取镜像
docker pull <image_name>:<tag>
# 推送镜像到仓库 
docker push <image_name>:<tag>
# 查看镜像详细信息
docker inspect <image_name>
# 显示镜像历史 
docker history <image_name>
# 给镜像打标签
docker tag <old_name> <new_name>
# 删除镜像 
docker rmi <image_name>
docker image rm <image_name>

 2. 容器运行测试 
# 运行新容器
docker run [options] <image_name>
# 后台运行并映射端口 
docker run -d --name my-container -p 8080:80 <image_name>
# 交互模式运行,退出即焚
docker run -it --rm <image_name> /bin/bash
# 注入环境变量
docker run -e ENV_VAR=value <image_name>

3. 常用基础服务启动
# 运行 Nginx 容器
docker run -d --name my-nginx -p 80:80 nginx
# 运行 MySQL 容器 (挂载数据并设置 root 密码) 
docker run -d --name mysql-db -e MYSQL_ROOT_PASSWORD=123456 -v mysql_data:/var/lib/mysql mysql:8.0

4. 调试与排错
# 列出运行中的容器 
docker ps
# 列出所有容器(包括停止的) 
docker ps -a
# 查看容器日志
docker logs <container_name>
# 实时查看容器日志 
docker logs -f <container_name>
# 查看实时日志示例
docker logs -f my-nginx
# 查看容器进程 
docker top <container_name>
# 进入运行中的容器 
docker exec -it <container_name> /bin/bash
docker exec -it <container_name> sh
# 进入容器调试示例
docker exec -it my-nginx /bin/bash

5. Docker Compose 
# 构建服务镜像 
docker-compose build
# 启动服务 
docker-compose up
# 后台运行服务 
docker-compose up -d
# 查看服务状态 
docker-compose ps
# 查看服务日志 
docker-compose logs
# 实时日志 
docker-compose logs -f
# 停止服务 
docker-compose down
# 停止服务并同时删除卷
docker-compose down -v

6.环境清理与离线迁移
# 删除构建缓存 
docker builder prune -f
# 清理所有构建缓存,包括失败的构建 
docker builder prune
# 清理所有构建缓存,包括内部缓存 
docker builder prune -a
# 删除所有悬空镜像(包括构建失败的中间层)
docker image prune
# 强制删除所有悬空镜像
docker image prune -f
# 删除所有未被使用的镜像(谨慎使用) 
docker image prune -a
# 导出镜像 
docker save -o <file.tar> <image_name>
# 导入镜像 
docker load -i <file.tar>

蟹蟹观看

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇