NUSGreyhats/ctfd-team-token-plugin

GitHub: NUSGreyhats/ctfd-team-token-plugin

CTFd 插件,为每个队伍的每个挑战签发唯一 token,并供外部挑战后端通过专用 API 安全解析身份。

Stars: 0 | Forks: 0

# CTFd Team Token 插件 CTFd 挑战类型插件,为每个 `(team, challenge)` 签发**基于 DB 的唯一 token**,并替换挑战描述中的 `{TEAM_TOKEN}`。外部服务通过特定于插件的服务器到服务器 API 解析 token。 请参阅 [docs/PRD.md](docs/PRD.md) 了解需求和验收标准。 ## 架构概述 该插件有两条运行时路径:CTFd 为玩家渲染 Team Token 挑战,以及受信任的外部挑战后端通过 CTFd 解析玩家提供的 token。外部服务永远不会收到 CTFd 的 admin token;它们只接收插件 API 密钥。 挑战查看请求路径: ``` flowchart LR Player["Team member browser"] ViewAPI["CTFd challenge API"] ChallengeType["TeamTokenChallenge"] Helpers["tokens.py helpers"] Database["CTFd database"] Player -->|view challenge| ViewAPI ViewAPI --> ChallengeType ChallengeType --> Helpers Helpers --> Database ``` Token 解析请求路径: ``` flowchart LR Backend["External challenge backend"] ResolveAPI["CTFd resolve API"] Settings["plugin config"] Helpers["tokens.py helpers"] Database["CTFd database"] Backend -->|resolve token| ResolveAPI ResolveAPI --> Settings ResolveAPI --> Helpers Helpers --> Database ``` Token 持久化规则: ``` flowchart TB Team["team_id"] Challenge["challenge_id"] Scope["Scope: team and challenge"] TokenRow["team_tokens row"] TokenValue["Opaque tt_ token"] Resolve["resolve_token()"] Solves["solves table"] Response["team, challenge, solved"] Team --> Scope Challenge --> Scope Scope -->|UNIQUE pair| TokenRow TokenRow -->|UNIQUE token| TokenValue TokenValue --> Resolve Solves -->|solved state| Resolve Resolve --> Response ``` 玩家挑战视图: ``` sequenceDiagram participant Team as Team member participant CTFd as CTFd challenge API participant Plugin as Team Token plugin participant DB as CTFd database Team->>CTFd: Open team_token challenge CTFd->>Plugin: read challenge and render fields Plugin->>DB: Find token by team and challenge alt Token row exists DB-->>Plugin: Existing tt_ token else First team view Plugin->>DB: Insert team_tokens row DB-->>Plugin: New tt_ token end Plugin-->>CTFd: Description and connection info with token CTFd-->>Team: Challenge modal with team token ``` 外部挑战解析流程: ``` sequenceDiagram participant Browser as Player browser participant Backend as External challenge backend participant API as CTFd resolve API participant Plugin as Team Token plugin participant DB as CTFd database Browser->>Backend: Send X-Team-Token Backend->>API: GET /resolve with bearer secret API->>API: Check enabled flag and secret API->>Plugin: resolve_token(token) Plugin->>DB: Read token, team, and solve rows DB-->>Plugin: Metadata, solve state, or no match Plugin-->>API: Resolve result API-->>Backend: HTTP status and JSON Backend-->>Browser: Unlock or reject ``` 解析结果: ``` flowchart TB Request["Resolve request"] Enabled{"API enabled"} Secret{"Bearer secret valid"} Token{"Token found"} Disabled["503 resolve_disabled"] Unauthorized["401 unauthorized"] Invalid["200 valid false"] Valid["200 valid true with solved flag"] Request --> Enabled Enabled -->|no| Disabled Enabled -->|yes| Secret Secret -->|no| Unauthorized Secret -->|yes| Token Token -->|no| Invalid Token -->|yes| Valid ``` ## 快速开始 (Docker) 在 repo 根目录下: ``` ./scripts/dev-up.sh python scripts/acceptance_tests.py ``` 或手动操作: ``` cd docker docker compose up -d --build docker compose --profile seed run --rm seed python ../scripts/acceptance_tests.py ``` 服务: | 服务 | URL | |---------|-----| | CTFd | http://localhost:8000 | | 示例外部挑战 | http://localhost:5001 | | 插件配置 | http://localhost:8000/admin/config → **Team Token** 选项卡 | 默认凭据: | 账户 | 密码 | 团队 | |---------|----------|------| | `admin` | `Password123!` | — | | `alpha` | `Password123!` | TeamAlpha | | `beta` | `Password123!` | TeamBeta | 开发环境插件 API 密钥(服务器到服务器):`dev-plugin-secret-for-testing` ## 在现有的 CTFd 上安装 克隆到 CTFd 的插件目录中(文件夹名称必须为 `team-token`): ``` git clone https://github.com/NUSGreyhats/ctfd-team-token-plugin.git CTFd/plugins/team-token ``` 重启 CTFd,然后打开 **Admin → Configuration → Team Token** 查看 resolve API 密钥。 仅用于开发的路径(`docker/`、`scripts/`、`example-challenge/`、`docs/`)与插件文件一起位于 repo 根目录;CTFd 会忽略它们。 ## 管理员工作流 1. 创建一个类型为 **team_token** 的挑战。 2. 在描述中的任意位置放置 `{TEAM_TOKEN}`。 3. 添加一个普通的静态 flag。 4. 仅与受信任的挑战后端共享插件 API 密钥。 示例描述: ``` Your token: `{TEAM_TOKEN}` curl -H "X-Team-Token: {TEAM_TOKEN}" https://your-challenge.example/start ``` ## 解析 API ``` GET /plugins/team-token/api/v1/resolve?token=tt_... Authorization: Bearer ``` 响应: ``` {"valid": true, "team_id": 1, "team_name": "TeamAlpha", "challenge_id": 3, "solved": false, "solved_at": null} {"valid": false} ``` 在 **Admin → Configuration → Team Token** 下配置密钥并启用/禁用 API。 resolve endpoint 是一个**路径**,而不是完整的 URL: ``` GET {CTFD_BASE_URL}/plugins/team-token/api/v1/resolve?token=tt_... Authorization: Bearer {team_token_plugin_secret} ``` 使用您的外部挑战服务器可以访问的任何 base URL(在 Docker 内部使用 `http://ctfd:8000`,在生产环境中使用您的公共 hostname)。CTFd 无法替您选择。 ## KOTH 排行榜 该插件还公开了一个只读的 KOTH 排行榜,访问地址为: ``` GET /koth GET /plugins/team-token/api/v1/koth ``` 它从 CTFd 的 awards 中派生出排行榜行,并且不会创建或更新任何数据库行。该页面每 5 秒从 JSON endpoint 刷新一次表格数据,而无需重新加载整个页面。排行榜读取结果会在每个 CTFd 进程中缓存 2 秒,以减少打开的浏览器标签页带来的重复数据库操作。每个公开榜单仅返回排名前 10 的参与者,并跳过那些不再指向可见 team 或 user 的 awards。默认情况下,它包含 category、name 或 description 看起来与 KOTH 相关的 awards,以及诸如 `koth-score-v1` 或 `rubikscube-score-v1` 的分数标记描述。 使用逗号分隔的 `category` 查询参数过滤一个或多个 award 类别: ``` /koth?category=watchdog-koth /plugins/team-token/api/v1/koth?category=watchdog-koth,my-long-snake-koth ``` 为了便于稳定解析,分数提交者应在 award 描述中包含一个 metric: ``` koth-score-v1 challenge=watchdog-koth cycles=344 score=1000 ``` 也支持诸如 `Best watchdog run: 344 cycles` 这样的描述。 ## 示例外部挑战 `example-challenge/` 服务演示了预期的流程: 1. 玩家从 CTFd 复制 `{TEAM_TOKEN}`。 2. 浏览器通过 `X-Team-Token` header 将其发送到 `POST /api/enter`。 3. 后端使用**插件密钥**(而不是 CTFd admin token)调用 CTFd resolve API。 4. 一旦 team 被识别且 `solved` 为 false,UI 即会解锁。 在 seeding 之后尝试: ``` curl -H "Authorization: Bearer dev-plugin-secret-for-testing" \ "http://localhost:8000/plugins/team-token/api/v1/resolve?token=YOUR_TEAM_TOKEN" ``` ## 插件布局 repo 根目录就是 CTFd 插件(用于 `git clone` 安装的标准布局): ``` team-token/ # clone target: CTFd/plugins/team-token ├── __init__.py ├── api.py ├── challenge.py ├── config.json ├── models.py ├── tokens.py ├── migrations/ ├── assets/ ├── templates/ ├── docker/ # local dev stack only ├── scripts/ ├── example-challenge/ └── docs/ ``` ## 开发说明 - Token 是在首次查看挑战时延迟创建的。 - 编辑挑战的管理员仍然会看到原始的 `{TEAM_TOKEN}` 占位符。 - MVP 仅针对 **team 模式**。 - 如果在本地使用,Docker CTFd 数据卷会在 `docker/.data/` 下被 gitignore。 ## 验收测试 在 stack 运行并 seeding 之后: ``` CTFD_RESOLVE_URL=http://localhost:8000/plugins/team-token/api/v1/resolve \ CTFD_TEAM_TOKEN_PLUGIN_SECRET=dev-plugin-secret-for-testing \ python scripts/acceptance_tests.py ``` 涵盖 PRD 测试 T1–T5、T6/T7 和 T8。
标签:API, CTFd插件, Python, Syscall, Web开发, 令牌管理, 无后门, 请求拦截, 逆向工具