写的如果不好见谅,作者水平有限
项目地址
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>
蟹蟹观看








