ctfhive/ctfhive
GitHub: ctfhive/ctfhive
CTFHive 是一个开源的自托管 CTF 赛事管理平台,为主办方提供从注册、题目管理到评分和实时记分板的完整竞赛基础设施。
Stars: 0 | Forks: 0
## 概述
CTFHive Public 是 CTFHive 背后的开源 CTF 赛事应用程序。它是一个独立的 Web 应用,用于举办 Capture the Flag 赛事,包含注册、团队、题目导入、附件分发、提交、评分、实时记分板以及后台管理操作。
本仓库面向希望自行部署公开 CTF 应用的主办方。[https://www.ctfhive.us/](https://www.ctfhive.us/) 上的托管版 CTFHive 控制面板是托管部署、托管工作流、产品信息和定价的公共入口。
题目托管有意设于本应用外部。主办方运行他们自己的题目基础设施,并将每个题目附加到一个 URL、命令或连接说明,类似于 CTFd。
## 包含内容
- 选手注册、登录、邮箱验证和密码重置流程
- 个人和团队参与
- 管理员题目 CRUD 和 YAML 题目导入
- 静态题目附件和按团队生成的构建产物
- 每个题目上的远程目标元数据
- Flag 提交、防作弊信号、分数事件和记分板冻结
- 用于赛事名称、赛事时间窗口、注册、团队规模和 UI 的管理员设置
- 本地开发支持 SQLite,部署支持 PostgreSQL
- 在 Redis 可用时使用基于 Redis 的缓存/速率限制,并提供本地回退
- 包含应用、PostgreSQL、Redis 和 nginx 的 Docker Compose 技术栈
## 快速开始
要求:
- Python 3.13
- `uv`
- Redis 对于本地开发是可选的
```
cp .env.example .env
uv sync --all-extras
uv run flask --app wsgi:app ctfhive seed --file data/example_challenges.yml
uv run flask --app wsgi:app run --debug --port 5000
```
打开 `http://localhost:5000`。
开发环境的引导管理员默认为:
- 用户名:`admin`
- 密码:`ChangeMeAdmin!2026`
首次登录会强制更改密码。在进行任何共享操作之前,请先更新 `.env`。
## Docker Compose
包含的 Compose 技术栈会启动带有 PostgreSQL、Redis 和 nginx 的公共 Web 应用。
```
cp .env.example .env
docker compose up --build
```
打开 `http://localhost:8080`。
持久化的本地目录:
- `data/` 用于存储生成的生产环境密钥
- `challenges/` 用于存储导入的题目工作区和生成的附件
- `uploads/` 用于存储管理员上传的文件
使用以下命令检查有效的 Compose 配置:
```
docker compose config
```
## 应用程序工作原理
CTFHive Public 是一个 Flask 应用程序,后端由 SQLAlchemy 模型、服务端渲染模板以及用于 flag、题目工作区、邮件、设置、分析和审计事件的服务模块提供支持。
| 组件 | 职责 |
| --- | --- |
| Flask 应用 | 路由、模板、表单、会话和请求处理。 |
| SQL 数据库 | 用户、团队、题目、flag、提交、解答、设置和审计事件。 |
| Redis | 可选的缓存、速率限制后端和 flag 镜像。 |
| 题目工作区 | 从导入的题目清单生成的本地文件。 |
| 管理 UI | 赛事设置、题目管理、用户、团队、评分和审计审查。 |
| CLI | 数据填充、导出、健康检查、测试运行和管理员创建。 |
典型的参与者流程:
1. 访客注册或登录。
2. 应用将用户关联到个人主体或团队主体。
3. 题目板列出可见的题目。
4. 参与者打开题目,下载附件,并使用显示的远程目标信息。
5. 参与者提交 flag。
6. Flag 服务针对特定于主体的题目 flag 验证提交的值。
7. 如果提交正确,将记录解答和分数事件。
8. 记分板根据数据库和缓存进行更新。
使用 `ADMIN_KEY`、主体的团队密钥和题目 ID 为每个主体和题目派生 Flag。这意味着同一题目可以针对不同的个人用户或团队具有不同的有效 flag。
## 配置
CTFHive Public 通过环境变量和可编辑的数据库设置进行配置。环境变量在启动时读取。应用启动后,可以通过 Web UI 更改管理员设置。
### 核心环境变量
| 变量 | 用途 |
| --- | --- |
| `APP_ENV` | `development`、`testing` 或 `production`。 |
| `SITE_URL` | 用于生成的链接和电子邮件的公共基础 URL。 |
| `CTF_NAME` | 首次启动时植入管理员设置的赛事名称。 |
| `CTF_DESCRIPTION` | 首次启动时植入管理员设置的赛事标语。 |
| `DEFAULT_FLAG_PREFIX` | 当题目未覆盖时使用的默认 flag 前缀。 |
| `EVENT_STARTS_AT` | 植入设置的 ISO 8601 赛事开始时间。 |
| `EVENT_ENDS_AT` | 植入设置的 ISO 8601 赛事结束时间。 |
| `CHALLENGES_ROOT` | 导入的题目工作区的文件系统路径。 |
| `MAX_CONTENT_LENGTH` | 上传的最大请求正文大小。 |
### 数据存储
| 变量 | 用途 |
| --- | --- |
| `DATABASE_URL` | SQLAlchemy 数据库 URL。本地可使用 SQLite;部署的赛事推荐使用 PostgreSQL。 |
| `REDIS_URL` | 用于缓存和 flag 镜像操作的 Redis URL。 |
| `RATELIMIT_STORAGE_URI` | 速率限制存储后端。在生产环境中请使用 Redis。 |
### 密钥
| 变量 | 用途 |
| --- | --- |
| `SECRET_KEY` | Flask 会话签名密钥。 |
| `ADMIN_KEY` | 用于按团队派生 flag 和审计链签名的密钥材料。 |
| `ENCRYPTION_KEY` | 用于加密数据库字段的密钥材料。 |
| `KEY_DIR` | 未显式设置密钥时,生产环境可在此目录持久化生成的密钥。 |
对于生产环境,请自行设置强密钥,或允许生产环境配置生成并将其持久化到 `KEY_DIR` 下。请将 `KEY_DIR` 保留在持久化存储上。
### 管理员引导
| 变量 | 用途 |
| --- | --- |
| `ADMIN_USERNAME` | 默认管理员用户名。 |
| `ADMIN_PASSWORD` | 默认管理员密码。 |
| `ADMIN_EMAIL` | 默认管理员电子邮件地址。 |
| `BOOTSTRAP_ADMIN_IN_PRODUCTION` | 仅当您有意在生产环境中进行首次启动管理员创建时,才设置为 `true`。 |
植入的管理员在使用应用之前将被强制更改默认密码。
### 安全和代理设置
| 变量 | 用途 |
| --- | --- |
| `SESSION_COOKIE_SECURE` | 通过 HTTPS 提供服务时设置为 `true`。 |
| `SESSION_COOKIE_SAMESITE` | Cookie SameSite 策略,通常为 `Lax`。 |
| `TRUST_PROXY` | 在 nginx 或其他受信任的反向代理后运行时启用。 |
| `SECURE_HEADERS_ENABLED` | 启用默认安全标头。 |
| `ENABLE_HSTS` | 启用 HSTS 标头。仅在 HTTPS 正常工作后使用。 |
### 电子邮件
| 变量 | 用途 |
| --- | --- |
| `EMAIL_VERIFICATION_ENABLED` | 启用账户验证和密码重置邮件流程。 |
| `MAILTRAP_API_KEY` | Mailtrap 发送 API 密钥。留空则禁用外发邮件。 |
| `MAIL_SENDER` | 赛事邮件的发件人地址。 |
| `MAIL_REPLY_TO` | 用于回复的主办方地址。 |
如果启用了电子邮件但未配置发送密钥,应用将记录日志并跳过邮件发送,而不会导致注册失败。
### Docker Compose 覆盖
Compose 技术栈会读取 `.env` 并覆盖容器网络的数据库/缓存设置。
| 变量 | 用途 |
| --- | --- |
| `COMPOSE_SITE_URL` | Compose 部署的公共 URL,默认为 `http://localhost:8080`。 |
| `COMPOSE_SESSION_COOKIE_SECURE` | 特定于 Compose 的安全 cookie 设置。 |
| `COMPOSE_TRUST_PROXY` | 特定于 Compose 的代理信任设置。 |
## 添加题目
CTFHive Public 支持两种题目工作流:
- 在管理 UI 中创建或编辑题目。
- 从 YAML 导入一个或多个题目。
CTFHive 存储题目元数据、附件、flag、解答状态和远程连接详情。它不运行远程题目服务。
### 管理 UI 工作流
1. 以管理员身份登录。
2. 打开 `Admin -> Challenges`。
3. 选择 `New Challenge`。
4. 填写标题、slug、类别、描述、积分、flag 前缀和状态。
5. 对于远程服务题目,启用 `Remote service challenge`。
6. 添加连接标签、目标 URL 和连接说明。
7. 附上任何参与者文件。
8. 保存题目。
起草时将状态设置为 `Hidden`,当参与者应该看到题目时设置为 `Visible`。
### YAML 导入
使用以下命令导入 YAML 文件:
```
uv run flask --app wsgi:app ctfhive seed --file data/example_challenges.yml
```
一个文件可以包含一个题目或顶级的 `challenges:` 列表。
### 远程题目示例
```
ctf_version: 1
slug: remote-web
title: Remote Web
category_slug: web
description_md: |
Visit the target and find the flag.
points: 150
status: visible
is_dynamic: true
connection_label: Web Target
connection_url: https://example.org/challenge
connection_info: |
This target is managed by the organizer.
Replace this URL with your event infrastructure before publishing.
files:
- name: notes.txt
inject_flag: false
contents: |
Enumerate the web app and submit the flag in the scoreboard.
```
### 题目清单字段
| 字段 | 必填 | 用途 |
| --- | --- | --- |
| `ctf_version` | 是 | 清单版本。使用 `1`。 |
| `slug` | 是 | 稳定的 URL 和导入标识符。 |
| `title` | 是 | 显示名称。 |
| `category_slug` | 是 | 诸如 `web`、`crypto`、`pwn`、`rev`、`forensics`、`misc`、`osint` 或 `blockchain` 之类的类别。 |
| `description_md` | 否 | 在题目页面上显示的 Markdown。 |
| `points` | 是 | 分值。 |
| `flag_prefix` | 否 | 可选的按题目的 flag 前缀。留空则使用 `DEFAULT_FLAG_PREFIX`。 |
| `status` | 否 | `hidden` 或 `visible`。默认为 `hidden`。 |
| `is_dynamic` | 否 | 在 UI 中将题目标记为远程服务题目。 |
| `connection_label` | 否 | 显示在目标详情上方的标签,例如 `Web Target` 或 `SSH Target`。 |
| `connection_url` | 否 | 目标 URL。HTTP/HTTPS URL 将渲染为可打开的链接。 |
| `connection_info` | 否 | 命令、凭据、说明或重置指令。 |
| `files` | 否 | 静态或生成的参与者文件。 |
未知字段将被拒绝,这样拼写错误就不会静默地改变题目行为。
### 文件和附件
文件可以是内联的:
```
files:
- name: README.txt
inject_flag: false
contents: |
Read this before starting.
```
或者可以引用暂存在清单旁边的文件:
```
files:
- path: handouts/source.py
name: source.py
inject_flag: false
```
`inject_flag: true` 会在生成打包文件时,将已知的占位符字符串替换为参与者派生的 flag。对于绝不应包含真实 flag 的公开源代码文件,请使用 `inject_flag: false`。
使用相同的 `slug` 重新导入清单会更新现有题目,而不是创建重复项。
使用以下命令导出当前题目元数据:
```
uv run flask --app wsgi:app ctfhive export-challenges --output challenges_export.yml
```
## 注入的 Flag、远程 Docker 目标和生成的静态输出
CTFHive Public 会验证按主体的 flag。个人选手或团队会为每个题目获得一个派生的 flag,并且提交的 flag 必须与该派生值匹配。公共应用本身不会将密钥注入到已经运行的外部服务中。它将 flag 注入到生成/下载的题目文件中。
使用以下模式之一:
- 远程 Docker 题目:生成按团队的 `target.env`、compose 文件或配置文件,供 Docker 服务作为 `FLAG` 使用。
- 静态生成输出题目:在文件生成时运行 `chal.py`,并写入按团队的谜题文件,例如 `challenge.txt` 或 `challenge.json`。
- 静态附件题目:重写已声明附件文件中的占位符,而无需运行 `chal.py`。
### Flag 注入工作原理
当参与者点击 `Generate Challenge Files` 时,CTFHive 将:
1. 将 `CHALLENGES_ROOT//` 复制到临时工作目录。
2. 将参与者派生的 flag 写入 `flag.txt`。
3. 除非该文件被标记为 `inject_flag: false`,否则重写文件中的 flag 占位符。
4. 仅当清单声明了 `output_file` 或将文件标记为 `generated: true` 时才运行 `chal.py`。
5. 将声明输出的文件复制到 `CHALLENGES_ROOT/.generated//principal-/`。
`flag.txt` 是内部文件。不要将 `flag.txt` 列在 `files` 中;schema 会拒绝它。生成器脚本应读取该文件并写入安全的面向参与者的构建产物。
识别的占位符值包括:
```
FLAG{placeholder}
FLAG{test}
FLAG{example}
CTFHIVE{placeholder}
CTFHIVE{test}
CTFHIVE{example}
FORGE{placeholder}
FORGE{test}
FORGE{example}
```
CTFHive 还会使用活动前缀重写 flag 形式的值,例如当 `DEFAULT_FLAG_PREFIX=FLAG` 时的 `FLAG{anything}`。
### 工作区目录结构
导入的题目会实体化在 `CHALLENGES_ROOT` 下。
```
challenges/
remote-docker/
remote-docker.yml # manifest written by import
chal.py # generator; private if not listed in files
Dockerfile # optional participant handout
app.py # optional participant handout
flag.txt # internal; auto-created/rewritten by CTFHive
.generated/
remote-docker/
principal-12/
target.env
.generation.json
```
如果在 YAML 中使用 `path:` 条目,则引用的文件必须在导入运行时已存在于 `CHALLENGES_ROOT/<>/` 内。对于完全可移植的 YAML,请使用内联的 `contents:` 条目。
### 情况 1:带有生成的 Flag 环境变量文件的远程 Docker 题目
当远程或参与者运行的 Docker 容器从环境变量中读取 flag 时使用此项。
推荐私有生成器的目录结构:
```
challenges/
remote-docker/
remote-docker.yml
chal.py
Dockerfile
app.py
```
在由 `CHALLENGES_ROOT` 配置的相同路径下创建该目录,然后从那里导入清单:
```
mkdir -p challenges/remote-docker
# 将 remote-docker.yml、chal.py、Dockerfile 和 app.py 放入该目录中
uv run flask --app wsgi:app ctfhive seed --file challenges/remote-docker/remote-docker.yml
```
`remote-docker.yml`:
```
ctf_version: 1
slug: remote-docker
title: Remote Docker
category_slug: web
description_md: |
Build and run the Docker challenge. The generated target.env file contains
the per-team FLAG value expected by the scoreboard.
points: 200
status: visible
is_dynamic: true
connection_label: Docker Target
connection_url: http://localhost:8081
connection_info: |
Generate your challenge files, then run:
docker build -t remote-docker .
docker run --rm --env-file target.env -p 8081:8080 remote-docker
output_file: target.env
files:
- name: Dockerfile
path: Dockerfile
inject_flag: false
- name: app.py
path: app.py
inject_flag: false
- name: target.env
generated: true
```
`chal.py`:
```
from pathlib import Path
flag = Path("flag.txt").read_text(encoding="utf-8").strip()
Path("target.env").write_text(f"FLAG={flag}\n", encoding="utf-8")
```
Docker 题目内的 `app.py` 示例:
```
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
FLAG = os.environ.get("FLAG", "FLAG{missing}")
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = f"flag: {FLAG}\n".encode()
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
```
`Dockerfile` 示例:
```
FROM python:3.13-slim
WORKDIR /app
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]
```
参与者流程:
1. 打开题目页面。
2. 点击 `Generate Challenge Files`。
3. 下载 `Dockerfile`、`app.py` 和 `target.env`。
4. 使用 `--env-file target.env` 构建/运行容器。
5. 将显示的 flag 提交到 CTFHive。
主办方托管的 Docker 流程:
1. 为每个个人选手或团队创建一个容器实例。
2. 将该主体生成的 `FLAG` 值传递给容器环境。
3. 将可访问的 URL 或命令放入 `connection_url` / `connection_info` 中。
4. 确保服务返回参与者提交的相同 flag。
除非您同时更改验证逻辑,否则不要对所有团队使用一个共享的固定 flag。CTFHive Public 需要按主体派生的 flag。
### 情况 2:仅 YAML 的远程 Docker 题目
如果您想要一个没有预存文件的单一 YAML 文件,请将附件内容内联。这对于小型示例很方便。
```
ctf_version: 1
slug: yaml-only-docker
title: YAML Only Docker
category_slug: web
description_md: Build the Docker service and use the generated env file.
points: 150
status: visible
is_dynamic: true
connection_label: Local Docker
connection_url: http://localhost:8081
output_file: target.env
files:
- name: Dockerfile
inject_flag: false
contents: |
FROM python:3.13-slim
WORKDIR /app
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]
- name: app.py
inject_flag: false
contents: |
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
FLAG = os.environ.get("FLAG", "FLAG{missing}")
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = f"flag: {FLAG}\n".encode()
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
- name: chal.py
inject_flag: false
contents: |
from pathlib import Path
flag = Path("flag.txt").read_text(encoding="utf-8").strip()
Path("target.env").write_text(f"FLAG={flag}\n", encoding="utf-8")
- name: target.env
generated: true
```
重要提示:由于 `chal.py` 列在 `files` 中,它会被视为已声明的参与者文件。对于真正的赛事,如果您不想发布生成器源代码,请首选上面的私有生成器结构。
### 情况 3:静态生成输出题目
当没有远程服务时使用此项。参与者下载生成的谜题构建产物并提交派生的 flag。
目录结构:
```
challenges/
runtime-static/
runtime-static.yml
chal.py
flag.txt # internal; auto-created/rewritten by CTFHive
```
`runtime-static.yml`:
```
ctf_version: 1
slug: runtime-static
title: Runtime Static
category_slug: crypto
description_md: |
Generate your personalized challenge file and recover the flag.
points: 100
status: visible
is_dynamic: false
output_file: challenge.txt
files:
- name: challenge.txt
generated: true
```
`chal.py`:
```
from pathlib import Path
flag = Path("flag.txt").read_text(encoding="utf-8").strip()
encoded = flag.encode("utf-8").hex()
Path("challenge.txt").write_text(
"Decode this hex string and submit the result:\n"
f"{encoded}\n",
encoding="utf-8",
)
```
生成输出题目的规则:
- `output_file` 必须与 `chal.py` 写入的文件相匹配。
- `chal.py` 在运行时,其当前工作目录设置为题目工作区副本。
- `chal.py` 必须在 20 秒内完成并退出且代码为 `0`。
- 生成的文件将复制到 `CHALLENGES_ROOT/.generated//principal-/`。
- 不要将原始 flag 打印到 stdout 或将其存储在额外的未声明文件中。
### 情况 4:带有占位符注入的静态附件
当可下载的附件本身需要包含参与者派生的 flag 时,在非常小的题目中使用此项。不要设置 `output_file`,也不要将任何文件标记为 `generated: true`;在该模式下 `chal.py` 不会运行。
```
ctf_version: 1
slug: injected-handout
title: Injected Handout
category_slug: misc
description_md: Generate and read your handout.
points: 50
status: visible
is_dynamic: false
files:
- name: README.txt
inject_flag: true
contents: |
Your flag is FLAG{placeholder}
```
对于绝不能包含答案的公开源代码、攻略、Dockerfile 或任何文件,请设置 `inject_flag: false`。
## 编辑设置
应用启动后,大部分赛事设置都可以从管理 UI 进行更改。
1. 以管理员身份登录。
2. 打开 `Admin -> Settings`。
3. 编辑字段。
4. 保存设置。
设置存储在数据库中。环境变量会在首次启动时植入初始值,但之后保存的管理员设置优先。
| 设置 | 用途 |
| --- | --- |
| Site / Event Name | 显示在导航、页面和电子邮件中。 |
| Site Tagline | 简短的公开赛事描述。 |
| Event Start | ISO 8601 开始时间。 |
| Event End | ISO 8601 结束时间。 |
| Max Team Size | 一个团队允许的最大用户数。 |
| Allow Solo Play | 允许用户在不加入团队的情况下参赛。 |
| Allow Solo-to-Team Switching | 允许个人用户稍后加入或创建团队。 |
| Registration Open | 允许注册新账户。 |
| Default Background | 新访客的默认动画背景。 |
使用具有时区意识的赛事时间窗口值,例如:
```
2026-07-01T12:00:00+00:00
```
其他管理页面可处理题目管理、用户管理、团队、记分板冻结、手动调整分数、解答分析和审计事件。
## 托管层级
CTFHive Public 是自托管的开源应用程序,不受托管计划的限制。托管的 CTFHive 控制面板为希望使用 CTFHive 运营的部署工作流的主办方提供托管层级。
托管控制面板:[https://www.ctfhive.us/](https://www.ctfhive.us/)
定价和限制可能会发生变化。请查看托管控制面板获取最新详情。以下层级曾于 2026 年 6 月 29 日公开列出。
| 层级 | 价格 | 年度 | 列出容量 | 最适合 |
| --- | --- | --- | --- | --- |
| Solo | `$25/mo` | `$250/yr` | 最多 128 名参与者,32 支队伍 | 个人训练和单机赛事。 |
| Standard | `$65/mo` | `$650/yr` | 最多 256 名参与者,64 支队伍 | 俱乐部、研讨会以及拥有完整团队赛的大学 CTF。 |
| Pro | `$125/mo` | `$1,250/yr` | 最多 512 名参与者,128 支队伍 | 更大规模的团队竞赛和更高的并发题目容量。 |
| Enterprise | 联系方式 | 定制 | 最多 1024 名参与者,256 支队伍 | 全公司或多赛事培训,提供 SSO 和定制支持。 |
这些层级适用于托管的 CTFHive 产品,而不适用于自托管的 CTFHive Public 运行时行为。自托管的公共应用没有计划限制。
## 生产环境注意事项
- 发布后保持 `SECRET_KEY`、`ADMIN_KEY` 和 `ENCRYPTION_KEY` 稳定。
- 在生产环境中使用 Redis 进行缓存和速率限制。
- 使用 HTTPS 并设置 `SESSION_COOKIE_SECURE=true`。
- 仅在受信任的反向代理后才启用 `TRUST_PROXY=true`。
- 将 `KEY_DIR`、数据库存储、题目工作区和上传保留在持久化存储上。
- 在赛事开始前和期间备份数据库和持久化目录。
- 如果开启了电子邮件验证,请在开放注册前测试邮件发送情况。
- 单独运行远程题目基础设施,并使用 `connection_url` 或 `connection_info` 从每个题目中进行链接。
## CLI 参考
```
# 导入 challenge YAML
uv run flask --app wsgi:app ctfhive seed --file data/example_challenges.yml
# 预览导入而不提交
uv run flask --app wsgi:app ctfhive seed --file data/example_challenges.yml --dry-run
# 导出 challenge
uv run flask --app wsgi:app ctfhive export-challenges --output challenges_export.yml
# 检查 app 健康状态
uv run flask --app wsgi:app ctfhive health
# 创建管理员用户
uv run flask --app wsgi:app ctfhive create-admin
# 重置数据库
uv run flask --app wsgi:app ctfhive reset-db --confirm
```
## 测试
```
uv run pytest
```
专项检查:
```
uv run pytest tests/test_public_edition.py -q
uv run pytest tests/test_importer.py tests/test_static_handout.py -q
uv run pytest tests/test_auth.py tests/test_team.py tests/test_scoreboard.py -q
```
可选的本地检查:
```
uv run python -m compileall -q ctfapp
docker compose config
```
标签:CTF平台, Docker, Flask, PostgreSQL, Python, 安全防御评估, 搜索引擎查询, 无后门, 测试用例, 请求拦截, 赛事系统, 逆向工具