官方地址: https://gzctf.gzti.me/zh

开始之前

当前资源: 一台公网服务器 , 一个域名

请先在 DNS 服务商将域名映射到服务器IP。若没有域名也可忽略,后续步骤差不多,类比参考即可。

需要注意的是:

Let’s Encrypt(免费 CA)

  • 从 2025 年 7 月起,Let’s Encrypt 已在 Staging(测试)环境开始签发 IP 地址证书,并计划在晚些时候推向生产环境。

  • 这类证书时效极短(约 6 天),只能通过 HTTP-01 或 TLS-ALPN-01 挑战(无法用 DNS-01)来证明对该 IP 的控制权

参考文档: https://letsencrypt.org/2025/07/01/issuing-our-first-ip-address-certificate/

在正式开始部署之前我们需要先阅读相关的文档。

我们只有一台服务器,那么直接采用docker单机部署。

参考: https://gzctf.gzti.me/zh/guide/deployment

缺点: 安全性较低,资源限制相关功能不够完善

然后我们可以先部署 1panel 面板,方便后面的 SSL 证书申请及部署,以及配置反向代理等

还可以直接跳过单独配置 docker (1panel自动化部署)

1panel参考文档: https://github.com/1Panel-dev/1Panel

然后登录面板 -> 应用商店安装 OpenResty (默认监听 80 443端口)

开始部署

初始化配置

现在我们创建一个 GZCTF 工作目录,新建文件 appsettings.json compose.yml

注意: 这里我直接在 root 环境部署,不推荐实际生产环境这么做,一般推荐创建一个专门的用户,授予满足正常运行条件的权限即可。

编辑 appsettings.json

注意 json 中不允许有注释,此处注释作为解释,实际运行请删除。

此处为参考配置,详细的 appsettings.json 配置字段介绍地址: https://gzctf.gzti.me/zh/config/appsettings

当前需要修改的初始化配置:

<Your POSTGRES_PASSWORD> <Your XOR_KEY> <Your PUBLIC_ENTRY>

{
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "Database": "Host=db:5432;Database=gzctf;Username=postgres;Password=<Your POSTGRES_PASSWORD>" // 数据库密码
  },
  "EmailConfig": {
    "SenderAddress": "",
    "SenderName": "",
    "UserName": "",
    "Password": "",
    "Smtp": {
      "Host": "localhost",
      "Port": 587
    }
  },
  "XorKey": "<Your XOR_KEY>",   // 此处配置加密密钥,用于加密数据库中比赛的私钥信息,可为任意长度的任意字符串。
  "ContainerProvider": {
    "Type": "Docker", // or "Kubernetes"  // 单机部署不修改
    "PortMappingType": "Default", // or "PlatformProxy"  
    "EnableTrafficCapture": false,
    "PublicEntry": "<Your PUBLIC_ENTRY>", // or "xxx.xxx.xxx.xxx" 容器后端的公网地址,用于生成比赛的访问地址,展示给参赛队伍。
    // optional
    "DockerConfig": {
      "SwarmMode": false,
      "Uri": "unix:///var/run/docker.sock"
    }
  },
  "CaptchaConfig": {
    "Provider": "None", // or "CloudflareTurnstile" or "HashPow"
    "SiteKey": "<Your SITE_KEY>",
    "SecretKey": "<Your SECRET_KEY>"
  },
  "ForwardedOptions": {
    "ForwardedHeaders": 7,
    "ForwardLimit": 1,
    "TrustedNetworks": ["192.168.12.0/8"]  // 可信网络,后续使用反向代理时,我们需要配置成docker网关IP
  }
}

编辑 compose.yml

当前需要修改的初始化配置:

<Your GZCTF_ADMIN_PASSWORD> <Your POSTGRES_PASSWORD> ports

services:
  gzctf:
    image: registry.cn-shanghai.aliyuncs.com/gztime/gzctf:develop
    restart: always
    environment:
      - "GZCTF_ADMIN_PASSWORD=<Your GZCTF_ADMIN_PASSWORD>"    //  平台初始管理员密码 // 用户名默认 admin
      # choose your backend language `en_US` / `zh_CN` / `ja_JP` ...
      - "LC_ALL=zh_CN.UTF-8"
    ports:
      - "80:8080"     // 外部80端口在前面1panel中被openresty占用了,我们修改为8080等端口都行 -> 8080: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
    restart: always
    environment:
      - "POSTGRES_PASSWORD=<Your POSTGRES_PASSWORD>"  // 数据库密码.与 appsettings.json 中一致
    volumes:
      - "./data/db:/var/lib/postgresql/data"

运行 docker compose up -d 启动

现在访问 <Your PUBLIC_ENTRY>:8080 就能正常进入平台啦。

// compose.yml 中最初配置端口映射为 8080:8080 会接收 0.0.0.0 的流量,仅仅是测试正常访问使用,后续方向代理时会修改重载

常见Q&A

如果在 Chrome 访问时出现这种情况,是网站主域或其他端口域配置了SSL证书,但是此8080端口没被证书覆盖到,造成的 HSTS安全性问题(协议自动升级)。具体细节不赘述

短期解决办法为 在 Chrome 搜索框输入: chrome://net-internals/#hsts

进入 HSTS 设置 在如图位置填入域名进行删除HSTS缓存就好了

要想永久解决,在后续会一并处理

然后再次访问的时候指定HTTP协议访问,就能正常访问啦。

反向代理

现在我们来为网站配置反向代理,将80 , 443 端口流量均代理到 8080 端口

访问登录 1panel 面板 -> 网站 -> 创建 -> 反向代理

主域名: example.com (你的域名)

其他域名: 如果映射有子域名也可以填上如: www.example.com

代理地址: 127.0.0.1:8080 (同一台主机内网,直接选HTTP减小开销)

其余按照要求填写即可。

确认后,点击配置:

image-vGKr.png

反向代理 -> 源文

我们需要确保存在下面配置: (为了OpenResty 反向代理的 Websocket兼容) (GZCTF 使用了Websocket消息通信)

proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

这里贴一个示例源文:

location ^~ / {
    proxy_pass http://127.0.0.1:8080; 
    proxy_set_header Host $host; 
    proxy_set_header X-Real-IP $remote_addr; 
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
    proxy_set_header REMOTE-HOST $remote_addr; 
    proxy_set_header Upgrade $http_upgrade; 
    proxy_set_header Connection $http_connection; 
    proxy_set_header X-Forwarded-Proto $scheme; 
    proxy_set_header X-Forwarded-Port $server_port; 
    proxy_http_version 1.1; 
    add_header X-Cache $upstream_cache_status; 
    add_header Cache-Control no-cache; 
    proxy_ssl_server_name off; 
    proxy_ssl_name $proxy_host; 
    add_header Strict-Transport-Security "max-age=31536000"; 
}

设置完成之后,我们保存。回到 网站 -> 重载 OpenResty

现在访问 http://example.com

反代成功

现在 ban 掉前面用于测试对0.0.0.0开启的8080端口监听,将 compose.yml 改为监听 127.0.0.1

// 安装的OpenResty 容器默认是以 --network=host 模式运行的, 容器内的 127.0.0.1 就是宿主机回环, 所以在 GZ 的 compose.yml 中监听 127.0.0.1 即可,如果不在此模式运行,那么我们需要 把两个容器放在同一个自定义网络,用服务名直连

然后在项目目录下执行重新挂载 docker 并重启。

docker compose down && docker compose up -d

现在尝试访问 example.com:8080 已经访问不到了。80端口依然正常反代

配置SSL证书

证书 -> Acme账户 -> 创建

邮箱随便填

回到 证书 -> 申请证书 -> 从网站中获取 -> 选择前面创建的网站 -> Acme账户 -> 选择刚刚创建的账户

主域名会自动填入,也可以自己调整好,当你设置有子域名 DNS 解析时,请将其他域名也一并填入,或者申请 *.example.com 的通配符域名。 验证方式选为 HTTP -> 确认

回到网站 -> 点击创建的网站的配置 -> HTTPS -> 启用HTTPS

HTTP选项根据需要自己修改,这里直接强制协议升级

选择刚刚申请的证书保存 然后重载

然后顺便为面板也使用 HTTPS , 避免因为同域下的协议升级导致前面的HSTS安全缓存问题。

面板设置 -> 安全 -> 启用面板SSL -> 直接使用前面申请的证书 -> 保存重载。(会需要重新登录)

现在我们访问 GZCTF 平台可以看到,全线HTTPS启用。

301跳转

注意,如果配置了 www 等子域名映射到同一个网站,我们最好将子域301到主域

网站 -> 配置 -> 重定向 -> 301 -> 目标主域

image-Yqaq.png

配置可信网络域

有时候我们需要在平台内部看到登录/操作IP,但是现在经过反向代理后我们的 IP 均会变为网关地址。

这时候就需要配置可信网络域,将反代传递的 XFF IP记录下来。

(图为已经调整好后的图)

docker网络可以从 面板的 容器 -> 网络看到。

也可以在项目目录下使用下面命令获取。

docker network inspect gzctf_default | grep -E '"Subnet"|"Gateway"'

现在对 appsettings.json 文件再次编辑:

如果没有则添加: ForwardedOptions

  "ForwardedOptions": {
    "ForwardedHeaders": 7,
    "ForwardLimit": 1,
    "ForwardedForHeaderName": "X-Forwarded-For",
  "TrustedNetworks": ["172.19.0.0/16", "127.0.0.0/8"],
  "TrustedProxies": ["172.19.0.1", "127.0.0.1"]
  }

然后在 1panel 重启或重新挂载均可以

docker compose down && docker compose up -d

然后IP记录将回归正常。

邮件配置

邮件发送有多种选择: 自建邮服 , 事务型发信服务 , 企业邮箱(163/腾讯企业邮等) , 个人邮箱(163/QQ 免费邮箱)等

综合因素我们选择 事务型发信服务 , 当然,如果你的 DNS 服务商直接免费提供邮件服务那更好。

这里我采用阿里云 DirectMail (总2000条邮件免费额度, 每日限额200条免费,按量也很便宜)

下面正式开始!

进入阿里云控制台,搜索 邮件推送 ,开通

来到 -> 邮件设置->发信域名 -> 新增域名 -> 填入你的域名。

点击配置

接下来我们将根据给出的要求配置,在DNS服务商处,进行配置

先来填写 域名记录处记录:

需要增添 spf 验证记录: 记录类型 txt , IP地址/目的地填写前面获取到的 spf 验证的记录值

然后增添 MX 验证: 记录类型 txt , IP地址/目的地填写前面获取到的 MX 验证的记录值, 优先级设置为 10

现在填写 子域名记录:

添加所有权验证记录: 子域名填写对应的主机记录,目的地填写记录值,类型TXT

添加DKIM验证记录,添加DMARC验证记录,方法和前面一样。注意公钥需要复制完全。

全部填写完后,等待DNS解析生效即可(一般5-30分钟)。然后进行验证,等待验证通过。

然后点击发信地址 -> 新建发信地址 -> 发信类型选择触发邮件 -> 其余按要求填写即可。

按要求设置 SMTP 密码

SMTP服务地址: smtpdm.aliyun.com ,SMTP服务端口号:25或80或465(SSL加密)。

现在回到 appsetting.json 进行编辑,如果没有就添加 EmailConfig

  "EmailConfig": {
    "SenderAddress": "你前面创建的发信地址",
    "SenderName": "发信名称,可自定义",
    "UserName": "发信名称,与发信地址相同即可",
    "Password": "SMTP密码",
    "Smtp": {
      "Host": "smtpdm.aliyun.com",
      "Port": 465
    },
    "BypassCertVerify": false
  },

重启或重新挂载。

docker compose down && docker compose up -d

进入平台,启用邮件验证

注册账号,进行验证,发送成功

端口范围设置

参考地址: https://gzctf.gzti.me/zh/guide/issues/port-range

在进行端口设置后,配合前面 HTTPS 的设置,会有一个新问题产生。即 分配的题目端口不再 SSL 证书信任范围之内,将会抛出如之前 HSTS 不信任的问题。

我们可以通过在最顶层的 OpenResty 容器 进行再次端口代理即可。

国家一级保护废物