lucafchala/restricted
GitHub: lucafchala/restricted
一个基于 Cloudflare Workers 和静态 HTML 构建的单页 CTF 解谜游戏,玩家需破解客户端混淆逻辑、计算 6 位访问码并提交至共享排行榜。
Stars: 1 | Forks: 0
# restricted.lucafchala.com
**在线访问:** [restricted.lucafchala.com](https://restricted.lucafchala.com) · **Worker:** `ctf-leaderboard.lucafchala.workers.dev` · **技术栈:** 静态 HTML + Cloudflare Worker (TypeScript) + Workers KV
它是 [lucafchala.com 生态系统](https://github.com/lucafchala/lucafchala.com#the-ecosystem) 的一部分(它**不**使用共享设计系统 —— 详情见[设计](#design))。
## 这是什么
**一句话概括:** `restricted` 是一个基于浏览器的 CTF,你需要通过客户端的门禁,计算出一个固定的 6 位访问码,并将其连同用户名提交给 Cloudflare Worker。Worker 会在服务端进行验证,并将你添加到存储在 KV 的共享排行榜中。
**一段话概述:** 整个挑战作为一个静态的 `index.html` 文件发布 —— 门禁、门禁后的“金库”以及排行榜 UI 都包含在其中,而逻辑和线索被故意混淆在客户端。获取密码只会解锁下一个阶段;真正的获胜条件是提交那唯一正确的访问码。提交操作会发送给一个用 TypeScript 编写的轻量级 Worker(`src/index.ts`),它保存着标准答案和排行榜。任何人都可以读取排行榜 —— 包括那些尚未解开谜题的人 —— 这也是游戏的一部分。
## 架构
```
restricted.lucafchala.com (static index.html)
│ gate → vault → leaderboard form (all client-side, obfuscated)
│
│ GET ?action=list ─────────────► ┌─────────────────────────────────────┐
└─ POST {username, code} ───────────► │ Worker: ctf-leaderboard │
│ src/index.ts │
│ • validates code server-side │
│ • appends entry to leaderboard │
│ • CORS: Access-Control-Allow-Origin: *
│ • KV binding "KV" → key "entries" │
└─────────────────────────────────────┘
```
- **前端:** 静态的 `index.html`,部署在 `restricted.lucafchala.com`。它实现了门禁逻辑(通过 `localStorage` 追踪带有冷却时间的硬编码尝试限制)和金库提交 UI。包含用于标记自动化探测的蜜罐路径/链接。
- **后端:** 单个 Cloudflare Worker(`src/index.ts`):
- `GET …?action=list` → 以 JSON 格式返回排行榜。
- `POST` 并附带 `{ username, code }` → 根据标准值验证 `code`,如果正确,则追加该条目。
- CORS 是开放的(`*`),以便静态页面可以进行跨域调用。
- **存储:** 一个 Workers **KV** 命名空间(绑定名为 `KV`),排行榜存储在 key `entries` 下。
- **`robots.txt`** 禁止访问 `/vault`、`/api/` 和 `/session`(诱饵路径)。
## 前置条件
- **Node.js** 和 **[Wrangler](https://developers.cloudflare.com/workers/wrangler/)** v4 (`npx wrangler`)。
- 一个 **Cloudflare** 账号,并绑定一个名为 `KV` 的 **KV 命名空间**(其 ID 已提交在 `wrangler.jsonc` 中;如果你 fork 了该项目,请替换为你自己的命名空间 ID)。
- 对于 CI 部署:需要一个 `CLOUDFLARE_API_TOKEN` GitHub Actions 密钥。
## 配置
`wrangler.jsonc`:
```
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "ctf-leaderboard",
"main": "src/index.ts",
"compatibility_date": "2025-02-04",
"observability": { "enabled": true },
"kv_namespaces": [
{ "binding": "KV", "id": "c240dc4587cd4ee890a20761aae811e5" }
]
}
```
| 名称 | 类型 | 描述 |
|---|---|---|
| `KV` | KV 绑定 | 存储排行榜(`entries` key) |
| `CLOUDFLARE_API_TOKEN` | GitHub Actions 密钥 | 供部署工作流用于发布 Worker |
无需其他密钥;访问码是 Worker 源码中的一个常量,而不是环境变量。
## 安装与部署
```
git clone https://github.com/lucafchala/restricted.git
cd restricted
# 本地运行 Worker
npx wrangler dev # serves src/index.ts; open index.html separately for the front end
# 部署 Worker
npx wrangler deploy
```
**CI:** `.github/workflows/deploy-worker.yml` 会在每次推送到 `main` 分支时(以及在手动触发 `workflow_dispatch` 时)部署 Worker,它使用 `cloudflare/wrangler-action@v3`,并配置了 `wranglerVersion: '4'` 和 `CLOUDFLARE_API_TOKEN` 密钥。静态的 `index.html` 由 `restricted.lucafchala.com` 提供(拥有独立的 Pages/托管设置)。
## 排行榜 API
```
GET https://ctf-leaderboard.lucafchala.workers.dev/?action=list
→ 200 { entries: [ { username, … } ] }
POST https://ctf-leaderboard.lucafchala.workers.dev/
body: { "username": "...", "code": "..." }
→ 200 on a correct code (entry appended) / rejected otherwise
```
响应包含宽松的 CORS 头,以便解谜页面可以直接从浏览器调用 Worker。
## 文件结构
```
.
├── index.html # The full CTF: gate + vault + leaderboard UI (obfuscated client logic)
├── src/
│ └── index.ts # Cloudflare Worker: GET leaderboard, POST validated submission, KV "entries"
├── wrangler.jsonc # Worker config (name "ctf-leaderboard", KV binding, compat date)
├── robots.txt # Disallows decoy paths (/vault, /api/, /session)
└── .github/workflows/
└── deploy-worker.yml # Deploy Worker on push to main / manual dispatch (wrangler-action v4)
```
## 设计
故意采用**非品牌化**风格 —— 这个谜题使用的是复古的、印刷文档的外观,而不是[共享的生态系统设计系统](https://github.com/lucafchala/lucafchala.com#design-system):
- **字体:** *IM Fell English*(衬线体)+ *Courier Prime*(等宽体),来自 Google Fonts。
- **调色板:** 暖纸色 —— `--bg #f5f0e1`, `--paper #ede8d5`, `--ink #1a1612`, `--rule #c8b89a`,并使用 `--red #8b1a1a` / `--green #1a4a1a` / `--link #00008b` 表示状态。
这种不协调是刻意为之的:`restricted` 是一个独立游戏,而不是网络页面,因此它不继承黄底黑字的视觉特征。
## 状态
**已投入生产环境**,作为一个在线谜题。排行榜是公开的,并持久化存储在 KV 中。
标签:Serverless, TypeScript, Web安全, 后端开发, 多模态安全, 安全插件, 排行榜, 程序员工具, 蓝队分析, 高对比度