GZCTF的搭建和CTF题目的出题

admin 2026-04-19 04:56:21 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细介绍了GZCTF平台的Docker部署流程,包括环境配置、邮箱验证设置及验证码集成,重点演示了Web和Pwn题目的动态Flag实现方法。通过具体代码示例展示了如何通过环境变量注入Flag、启动脚本替换占位符及权限控制,为CTF出题提供完整技术方案。 综合评分: 78 文章分类: CTF,安全工具,WEB安全,二进制安全,安全开发


cover_image

GZCTF的搭建和CTF题目的出题

三社院信息Sec 三社院信息Sec

三社院信息Sec

2026年4月9日 19:26 河南

在小说阅读器读本章

去阅读

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

项目地址

https://github.com/GZTimeWalker/GZCTF

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

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

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

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

创建 nano compose.yml

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

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

配置好了启动就行

docker compose up -d

有问题阿里云镜像加速

mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
&nbsp;&nbsp;"registry-mirrors": [
&nbsp; &nbsp;&nbsp;"https://docker.1panel.live",
&nbsp; &nbsp;&nbsp;"https://docker.m.daocloud.io",
&nbsp; &nbsp;&nbsp;"https://dockerhub.icu",
&nbsp; &nbsp;&nbsp;"https://registry.cn-hangzhou.aliyuncs.com"
&nbsp; ]
}
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": {
&nbsp; &nbsp;&nbsp;"Provider":&nbsp;"CloudflareTurnstile",
&nbsp; &nbsp;&nbsp;"SiteKey":&nbsp;"key",
&nbsp; &nbsp;&nbsp;"SecretKey":&nbsp;"key",

Web出题和动态 flag

GZCTF动态flag 变量是GZCTF_FLAG

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

随便写一下题目

目录结构

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

index.php (题目源码)

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

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

flag.sh (启动脚本)

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

#!/bin/sh

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

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

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

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

Dockerfile

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

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

# 替换 Debian 软件源为阿里云源,加速后续可能需要的扩展安装
RUN sed -i&nbsp;'s/deb.debian.org/mirrors.aliyun.com/g'&nbsp;/etc/apt/sources.list && \
&nbsp; &nbsp; sed -i&nbsp;'s/security.debian.org/mirrors.aliyun.com/g'&nbsp;/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'
{
&nbsp;&nbsp;"registry-mirrors": [
&nbsp; &nbsp;&nbsp;"https://docker.1panel.live",
&nbsp; &nbsp;&nbsp;"https://docker.m.daocloud.io",
&nbsp; &nbsp;&nbsp;"https://docker.nyist.machengqiang.com"
&nbsp; ]
}
EOF

重启 Docker 服务让配置生效

sudo systemctl daemon-reload
sudo systemctl restart docker

创建题目动态flag

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

设置

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

你就会发现是动态flag了

Pwn的出题和动态flag

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

目录结构

pwn/
├── Dockerfile
├── flag.sh
└── src/
&nbsp; &nbsp; └── pwn.c

pwn.c

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

#include&nbsp;<stdio.h>
#include&nbsp;<stdlib.h>

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

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

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

&nbsp; &nbsp;&nbsp;return&nbsp;0;
}

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

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

#!/bin/sh

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

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

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

# 4. 切换到低权限用户 ctf,使用 socat 监听 80 端口并将流重定向到 pwn 二进制文件
# EXEC 模式下,每次有 nc 连接进来,都会 fork 一个新的 pwn 进程
exec&nbsp;su ctf -c&nbsp;"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&nbsp;'s@//.*archive.ubuntu.com@//mirrors.aliyun.com@g'&nbsp;/etc/apt/sources.list && \
&nbsp; &nbsp; sed -i&nbsp;'s/security.ubuntu.com/mirrors.aliyun.com/g'&nbsp;/etc/apt/sources.list

# 安装 gcc (用于编译源码) 和 socat (用于端口转发)
RUN apt-get update && \
&nbsp; &nbsp; DEBIAN_FRONTEND=noninteractive apt-get install -y gcc socat && \
&nbsp; &nbsp; 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 && \
&nbsp; &nbsp; chown root:ctf /home/ctf/pwn && \
&nbsp; &nbsp; chmod 750 /home/ctf/pwn && \
&nbsp; &nbsp; 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&nbsp;history&nbsp;<image_name>
# 给镜像打标签
docker tag <old_name> <new_name>
# 删除镜像
docker rmi <image_name>
docker image rm <image_name>

&nbsp;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&nbsp;exec&nbsp;-it <container_name> /bin/bash
docker&nbsp;exec&nbsp;-it <container_name> sh
# 进入容器调试示例
docker&nbsp;exec&nbsp;-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>

蟹蟹观看


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:三社院信息Sec 三社院信息Sec 三社院信息Sec《GZCTF的搭建和CTF题目的出题》

AIAgent导致杀伤链失效 网络安全文章

AIAgent导致杀伤链失效

文章总结: 文章分析AI代理被攻陷后对传统网络杀伤链模型的颠覆性影响。攻击者通过控制已拥有广泛权限的AI代理,可直接利用其正常工作流程进行数据窃取和横向移动,从
评论:0   参与:  0