svuillaume/forticnapp-ctfd
GitHub: svuillaume/forticnapp-ctfd
一个基于 CTFd 构建的云安全夺旗赛平台,能将 FortiCNAPP 的真实安全发现自动转化为竞赛题目,用于售前演示和客户研讨会。
Stars: 1 | Forks: 0

# FortiCNAPP CTF
**一个用于 FortiCNAPP 研讨会和客户演示的夺旗赛(CTF)平台**




## 它是什么?
FortiCNAPP CTF 将真实的云安全发现转化为有积分和计时的竞赛。
参与者需要在 FortiCNAPP 控制台中调查告警、对漏洞进行分类排查并找出合规违规情况,
然后在实时排行榜上提交答案。
**两种模式,同一技术栈:**
| 模式 | 挑战题 | 需要 FortiCNAPP 租户 | 最适用于 |
|---|---|---|---|
| **CTF 实验室** | 21 道手工编写的题目 | ❌ 否 | 演示、离线、彩排 |
| **实时 CTF** | 基于真实发现生成 | ✅ 是 | 使用真实租户的现场活动 |
首次启动时,**会自动加载 5 道随机的 CNAPP 热身题** —— 无需任何设置。
## 架构
```
Browser
│
│ https://localhost (CTFd — port 443)
│ https://localhost:5555 (Trigger API)
│
▼
┌──────────────────────────────────────────────────────────┐
│ Caddy (HTTPS reverse proxy — self-signed cert) │
└────────────┬──────────────────────────────┬─────────────┘
│ │
:8000 ▼ :5555 ▼
┌──────────────────┐ ┌──────────────────────────┐
│ CTFd 3.7.5 │ │ Trigger Service (Flask) │
│ challenges │◀──────│ /run/static │
│ scoring │ │ /run/dynamic │
│ leaderboard │ │ /reset │
└────────┬─────────┘ └──────────────────────────┘
│ │
┌──────┴──────┐ ┌──────┴──────────────────────┐
│ MariaDB │ │ bridge-static │
│ Redis │ │ YAML challenges → CTFd │
└─────────────┘ │ │
│ bridge (dynamic) │
│ FortiCNAPP API → CTFd │
└─────────────────────────────┘
```
## 前置条件
- **Docker** 和 **Docker Compose v2** — `docker compose version`
- **Python 3.10+** — 用于运行 `ctl.py`
- 主机上需开放 **80、443、5555 端口**
## 快速开始
```
python ctl.py
```
首次运行时,设置向导会自动启动。它将引导您完成三个部分:
| 部分 | 询问内容 |
|---|---|
| **CTFd** | 数据库密码、管理员用户名 / 邮箱 / 密码 |
| **HTTPS** | 主机名或 IP —— 留空则使用 `localhost` |
| **FortiCNAPP API** | Account、Key ID、Secret —— 仅在实时 CTF 模式下需要 |
然后按 **`1` (START)**。脚本将执行以下操作:
1. 启动 `db`、`cache`、`ctfd`
2. 自动完成 CTFd 首次运行向导(无需浏览器操作)
3. 生成管理员 API token 并保存到 `.env`
4. 启动 `trigger` 和 `caddy`
5. 应用 Fortinet 主题
6. 如果数据库为空,则加载 5 道随机的 CNAPP 热身题
打开 **`https://localhost`** —— 接受一次浏览器的安全警告(由于使用自签名证书)。
## 主页
CTFd 主页包含两个模式卡片和一个重置控件:
| 按钮 | 操作 |
|---|---|
| **加载 CTF 实验室挑战题** | 加载 21 道静态挑战题;解锁 CTF 实验室的 *Start Challenges* 按钮 |
| **加载实时挑战题** | 从您的 FortiCNAPP 租户中拉取安全发现;解锁实时 CTF 的 *Start Challenges* 按钮 |
| **重置**(任意卡片) | 清除所有挑战题并重新加载 5 道随机的 CNAPP 热身题 |
## 控制面板 (`ctl.py`)
```
╔══════════════════════════════════════════╗
║ FortiCNAPP CTF — Control Panel ║
╚══════════════════════════════════════════╝
STATUS
● CTFd ● DB ● Cache ● Trigger ● Caddy
s Setup / edit .env
1 START → https://localhost
2 STOP (containers stopped, data kept)
3 RESTART
4 DESTROY ⚠️ removes containers + volumes — all data lost
5 Logs (CTFd)
6 Logs (Trigger)
q Quit
```
| 选项 | 功能 |
|---|---|
| `s` | 重新打开设置向导以编辑任何 `.env` 值 |
| `1` | 启动完整技术栈 —— 首次运行时会自动配置 CTFd |
| `2` | 停止所有容器(数据保留在 Docker volumes 中) |
| `3` | 停止后重新启动 |
| `4` | 停止 → 移除所有容器及 volumes → 清除管理员 token |
| `5` / `6` | 跟踪查看 CTFd / Trigger 的日志 |
## Flag 格式
### 热身题(默认 / 重置)
纯文本答案 —— 无需包含包装格式:
```
transport_layer_security
cloud_security_posture_management
misconfiguration
```
挑战描述中始终会显示预期的格式,例如 `Example format: word_word_word`。
### CTF 实验室和实时 CTF 挑战题
标准 CTF flag 格式:
```
FLAG{answer}
```
| 类别 | 答案是… |
|---|---|
| 告警分诊 | MITRE 技术 ID —— `FLAG{T1496}` |
| 主机安全 | CVE ID 或主机名 —— `FLAG{CVE-2025-12345}` |
| 容器安全 | 镜像名称、端口或缩写 |
| 云合规 | CIS 控制编号 —— `FLAG{1.5}` |
所有 flag 均不区分大小写。
## 模式详解
### CTF 实验室(静态)
包含 21 道手工编写的挑战题,涵盖了真实的 FortiCNAPP 场景。无需凭证。可离线运行。
| 类别 | 数量 | 主题 |
|---|---|---|
| 🔴 告警分诊 | 5 | MITRE ATT&CK T1496 · T1078.004 · T1571 · 复合告警 |
| 🟠 主机安全 | 5 | CVE ID · CVSS 评分 · 主机名查询 · 无代理扫描 |
| 🔵 容器安全 | 5 | 影子 MCP · 挖矿木马 · Docker 取证 · 端口暴露 |
| 🟡 云合规 | 6 | CIS AWS 1.5 · 1.14 · 2.1.5 · 3.1 · 5.2x · CSPM |
挑战题文件:`static_ctf/ctf/*/challenges.yml` —— 可自由编辑并重新加载。构建器是完全幂等的(首次运行时创建,再次运行时更新)。
### 实时 CTF(动态)
从您的 FortiCNAPP 租户中拉取真实的安全发现,并自动生成挑战题。
**FortiCNAPP 控制台 → Settings → API Keys → Create New**
下载 JSON 文件 —— 其中包含 `keyId`、`secret` 和 `account`。在设置向导中输入它们,或直接添加到 `.env` 文件中:
```
FORTICNAPP_ACCOUNT=acme-prod
FORTICNAPP_SUBACCOUNT=
FORTICNAPP_API_KEY_ID=ACME_1234...
FORTICNAPP_API_SECRET=_your_secret
LOOKBACK_HOURS=72
MAX_CHALLENGES_PER_CATEGORY=5
```
| 类别 | FortiCNAPP API endpoint |
|---|---|
| 告警分诊 | `/api/v2/Alerts`(带有 MITRE 标签的告警) |
| 主机安全 | `/api/v2/Vulnerabilities/Hosts/search` |
| 容器安全 | `/api/v2/Vulnerabilities/Containers/search` |
| 云合规 | `/api/v2/Configs/ComplianceEvaluations/search` |
```
MOCK_MODE=true
```
**数据脱敏** —— 当设置 `SANITIZE=true`(默认值)时,桥接程序会清除所有可识别客户身份的数据:AWS 账户 ID、Azure 订阅 UUID、GCP 项目 ID、S3 存储桶名称、公网 IP、电子邮件地址和主机名。每次运行的映射关系是稳定的,因此挑战题的描述能保持内在一致。
## HTTPS
Caddy 使用 `tls internal` —— 其内置的本地 CA 会在启动时颁发自签名证书。无需域名或互联网连接。
**首次访问:** 浏览器会显示安全警告。点击 **Advanced → Accept**(Chrome/Edge)或 **Accept the Risk**(Firefox)。每个浏览器只需操作一次。
**自定义主机名或 IP**(适用于同一网络下的参与者):
```
# .env
FQDN=192.168.1.100
```
Caddy 将为该地址颁发证书。参与者只需接受一次警告。
## 活动工作流
### 活动前(约 5 分钟)
```
# 1. 启动一切
python ctl.py → 1
# 2. 加载你想要的挑战模式
# — 使用主页按钮,或通过 CLI:
docker compose run --rm bridge-static # CTF Lab
docker compose run --rm bridge # Live CTF
# 3. 使挑战可见
# Admin Panel → Configs → Challenge Visibility → Public
```
### 活动进行中
1. 参与者在 `https://your-host` **注册**
2. **阅读**挑战场景
3. **浏览** FortiCNAPP 控制台以寻找答案
4. **提交** flag —— 系统即时评分
5. 观看 **实时排行榜** 更新
**建议的 45 分钟流程:**
| 时间 | 轮次 | 技能点 |
|---|---|---|
| 0–2 分钟 | 场景设置 | — |
| 2–17 分钟 | 🔴 告警分诊 | MITRE 映射加速事件响应 (IR) |
| 17–32 分钟 | 🟠🔵 主机 + 容器安全 | CWPP 相比独立扫描器的价值 |
| 32–42 分钟 | 🟡 云合规 | CSPM + 审计案例 |
| 42–45 分钟 | 复盘 + 排行榜 | 在控制台中现场演示一个发现结果 |
### 活动后 —— 完全重置
```
python ctl.py → 4 → YES
```
停止所有容器并清除所有 volumes(积分、用户、挑战题)。按 `1` 可从头开始重新构建。
## 添加自定义挑战题
编辑或创建 `static_ctf/ctf/
/challenges.yml` 中的 YAML 文件,然后重新加载:
```
docker compose run --rm bridge-static
```
**最小化挑战题模板:**
```
challenges:
- name: "My Challenge"
author: "Your Name"
category: "Alert Triage"
description: |
Find the alert triggered by technique **T1496** and submit the MITRE ID.
value: 100
type: standard
flags:
- content: "FLAG{T1496}"
type: static
hints:
- content: "Check the Alert Triage dashboard in FortiCNAPP."
tags:
- mitre
state: visible
```
## 项目布局
```
forticnapp-ctf/
├── ctl.py # ← single entry point
├── docker-compose.yml
├── .env.example # copy to .env — never commit .env
│
├── static_ctf/ # CTF Lab (static mode)
│ ├── build.py # entrypoint: reads env → builds CTF
│ ├── ctfbuilder.py # idempotent challenge push
│ ├── ctfd.py # CTFd REST API wrapper
│ ├── fortinet.css # Fortinet dark theme
│ ├── home.html # home page (mode selector cards)
│ └── ctf/
│ ├── config.yml
│ ├── 1_Alert Triage/
│ ├── 2_Host Security/
│ ├── 3_Container Security/
│ └── 4_Cloud Compliance/
│
├── forticnapp_ctf_api/ # Live CTF (dynamic mode)
│ ├── bridge.py # pull → sanitize → push
│ ├── forticnapp_client.py # FortiCNAPP v2 API client
│ ├── ctfd_client.py # CTFd admin API client
│ ├── challenges.py # finding → Challenge mapping
│ └── sanitize.py # PII scrubber
│
├── trigger/ # Always-on trigger service
│ └── app.py # Flask: /run/static /run/dynamic /reset
│ # Auto-configures CTFd on first boot
│ # Auto-resets after inactivity
│
├── caddy/ # HTTPS reverse proxy
│ └── Caddyfile # tls internal — self-signed cert
│
└── sample_data/ # Mock data for MOCK_MODE=true
├── alerts.json
├── container_vulns.json
├── host_vulns.json
└── compliance.json
```
## 常用命令
```
# 启动一切
python ctl.py
# 加载 / 重新加载挑战
docker compose run --rm bridge-static # CTF Lab
docker compose run --rm bridge # Live CTF
# 仅重新应用 Fortinet 主题(不更改挑战)
docker compose run --rm bridge-static --theme-only
# 查看日志
docker compose logs -f ctfd
docker compose logs -f trigger
# 检查 container 健康状态
docker compose ps
# 代码更改后重建 image
docker compose build && docker compose up -d
# 检查不活动自动重置计时器
curl https://localhost:5555/status/inactivity
```
## 故障排除
**浏览器显示安全警告**
属于预期情况 —— 自签名证书。每个浏览器点击一次 **Advanced → Accept** 即可。
**主页空白或显示默认的 CTFd 主题**
点击主页底部的 **Re-apply Fortinet Theme**,或者运行:
```
docker compose run --rm bridge-static --theme-only
```
**Start Challenges 按钮被锁定 (🔒)**
请先点击 **Load CTF Lab Challenges** 或 **Load Live Challenges**。成功加载后,该按钮将自动解锁。
**CTFd 不断重启**
检查 `.env` 中是否设置了 `SECRET_KEY`(由 `ctl.py` 自动生成)。
**管理员 token 被拒绝 (401)**
运行 `python ctl.py` → `s` → 从 **Admin Panel → Settings → Tokens** 粘贴一个新的 token。
**主页显示 "already running"(已在运行)**
系统正在构建中。请等待约 1 分钟,或检查 `docker compose logs trigger`。
**未生成任何挑战题 (实时 CTF)**
1. 检查 `.env` 文件:`FORTICNAPP_ACCOUNT`、`FORTICNAPP_API_KEY_ID`、`FORTICNAPP_API_SECRET`
2. 扩大时间窗口:`LOOKBACK_HOURS=720`
3. 离线测试:`MOCK_MODE=true`
**容器安全返回结果为 0 (动态模式)**
必须在租户上启用无代理工作负载扫描。使用 `LOOKBACK_HOURS=720` 或 `MOCK_MODE=true`。
**参与者无法看到挑战题**
Admin Panel → Configs → Challenge Visibility → 选择 **Public**。
**构建时出现 Docker 层缓存错误**
```
docker builder prune -f
docker compose build --no-cache
docker compose up -d
```
## 许可证与致谢
CTFd 采用 [BSD-2-Clause 许可证](https://github.com/CTFd/CTFd/blob/master/LICENSE)。
FortiCNAPP 和 Lacework 是 Fortinet, Inc. 的商标。
本项目与 CTFd 没有官方附属关系。标签:搜索引擎查询, 版权保护, 请求拦截, 逆向工具