BrowserLaboratory/wxl-template

GitHub: BrowserLaboratory/wxl-template

一个无需服务器的纯前端 Web 漏洞利用 CTF 训练平台模板,通过 WebAssembly 在浏览器中模拟真实后端环境。

Stars: 0 | Forks: 0

# Web eXploitation Laboratory (WXL) [![Quality Gates](https://static.pigsec.cn/wp-content/uploads/repos/cas/bc/bc5c82c649aa06d1f928f7cfe4b5c5a4ea47052c5c400e065d3a338cd38d357e.svg)](https://github.com/BrowserLaboratory/wxl-template/actions/workflows/quality-gates.yml) [![License: ECL-2.0](https://img.shields.io/badge/License-ECL--2.0-blue.svg)](LICENSE) [![VitePress](https://img.shields.io/badge/VitePress-2.0.0--alpha.16-green.svg)](https://vitepress.dev) [![pnpm](https://img.shields.io/badge/pnpm-10.28.0-orange.svg)](https://pnpm.io) ## 概述 **Web eXploitation Laboratory (WXL)** 是一个 CTF 风格的 Web 漏洞利用训练平台。每个挑战都在浏览器中完全运行:WebAssembly 模拟了真实的后端环境,因此该平台无需任何服务器基础设施即可部署和使用。 ### 核心功能 - **纯前端执行**:Service Worker 拦截 HTTP 请求并在浏览器中模拟后端行为。 - **多种后端 runtime**:支持 Python Flask / FastAPI(通过 Pyodide)以及 PHP(通过 php-wasm)。 - **加密的虚拟文件系统**:flag 和应用资产在 AES-GCM-256 加密下存储,防止直接读取。 - **静态部署**:构建输出为纯静态文件,可托管于任何静态服务(GitHub Pages、Cloudflare Pages 等)。 ## 技术栈 | 层级 | 技术 | |------|------| | 文档框架 | [VitePress](https://vitepress.dev) 2.0.0-alpha.16 | | UI 框架 | [Vue 3](https://vuejs.org) 3.5 + [UnoCSS](https://unocss.dev) | | 状态管理 | [Pinia](https://pinia.vuejs.org) 3 | | Python runtime | [Pyodide](https://pyodide.org) 0.29 | | PHP runtime | [php-wasm](https://github.com/seanmorris/php-wasm) | | WASM 模块 | Rust 2021 + [wasm-pack](https://rustwasm.github.io/wasm-pack/) | | 攻击会话追踪 | IndexedDB (`idb` 包) attack-session 持久化 | | 包管理器 | [pnpm](https://pnpm.io) 10.28 | ## 前置条件 - **Node.js** >= 22.6 — `challenge:keygen`、`create:challenge`、`challenge:validate` 和 `challenge:analyze` 通过 `node --experimental-strip-types` 运行,这在早期版本中不可用。其余 TypeScript 脚本通过内置的 `tsx` 运行,没有此版本限制。CI 在 Node 24 上构建。 - **pnpm** >= 10 (`npm install -g pnpm`) - **Rust** 工具链(通过 [rustup](https://rustup.rs/) 安装) - **wasm-pack** (`cargo install wasm-pack`) — 不是包依赖项;请将其安装到你的 Rust 工具链中 - **wasm-tools** (`cargo install wasm-tools` 或 `pnpm wasm:tools`) — `pnpm challenge:verify` 的 L2 阶段所需,该阶段会对生成的 payload 运行 `wasm-tools validate`。`pnpm challenge:keygen` 也会在 strip 和 mutate 过程中使用它,但如果不存在,则会降级为警告。 - **Chromium for Playwright** — 在首次运行 `pnpm challenge:verify` 或 `pnpm test:smoke` 之前需要。在 `pnpm install` 之后,使用以下命令安装一次浏览器二进制文件: pnpm exec playwright install chromium ## 快速开始 ``` # 1. Clone the project git clone https://github.com/BrowserLaboratory/wxl-template.git cd wxl-template # 2. 安装 Node.js 依赖 pnpm install # 3. 构建 WASM 模块并启动 dev server pnpm dev ``` 开发服务器默认在 `http://localhost:5173` 启动。 ## 可用脚本 | 命令 | 描述 | |------|------| | `pnpm dev` | 构建 WASM 模块并启动开发服务器 | | `pnpm build` | 构建 WASM 模块并输出静态站点 | | `pnpm docs:dev` | 仅启动 VitePress 开发服务器(跳过 WASM 构建) | | `pnpm docs:build` | 仅构建 VitePress 静态站点 | | `pnpm docs:preview` | 预览构建好的静态站点 | | `pnpm test` | 运行 TypeScript / JavaScript 单元测试 (Vitest)。直接运行会停留在 watch 模式 — 使用 `pnpm test --run` 进行单次运行 | | `pnpm test:smoke` | 针对构建好的站点运行 Playwright 冒烟测试 | | `pnpm wasm:build` | 构建所有 Rust WASM 模块 | | `pnpm wasm:test` | 运行 Rust 单元测试 (`cargo test`) | | `pnpm wasm:tools` | 尝试将 `wasm-tools` 安装到 Rust 工具链中;它会屏蔽失败并总是以退出码 0 退出,因此请通过 `wasm-tools --version` 进行确认 | | `pnpm fork:init` | 在 fork 后重写项目身份(作者、GitHub URL,以及可选的 VitePress `base`)。需要 `--author` 和 `--repo`;包名仅在带有 `--name` 或 `--rebrand` 时更改,且 `deploy.yml` 中的 `SITE_BASE` 永远不会被修改 | | `pnpm challenge:keygen` | 为每个挑战生成加密的 WASM 模块 | | `pnpm create:challenge` | 根据 flag 脚手架生成新挑战;必须提供 `--name `,直接运行会以退出码 1 退出并显示用法说明 | | `pnpm challenge:validate` | 验证每个挑战的 frontmatter 和文件布局 | | `pnpm challenge:analyze` | 报告挑战的内容和配置 | | `pnpm challenge:retype` | 修改现有挑战的后端 / 难度 / 标签 / 分类 | | `pnpm challenge:verify` | 对挑战运行分层验证门控(L1 lint,L2 构建,L3 Playwright e2e) | | `pnpm challenge:verify:blind` | 独立运行 L4 盲解子程序(也可通过 `pnpm challenge:verify --blind` 触发) | | `pnpm challenge:verify:cross` | 仅限维护者的 L4 多智能体交叉检查 — 针对 `claude,codex,gemini` 运行盲测门控并汇总判定结果 | | `pnpm prepare` | 安装 git hooks (`simple-git-hooks`);在 `pnpm install` 之后自动运行 | ## 架构 ``` Browser ├── VitePress site (Vue 3 + UnoCSS) │ ├── Challenge pages (Markdown + YAML frontmatter) │ └── IndexedDB (attack-session persistence + tool state) ├── Service Worker (docs/public/challenge-sw.js) │ └── Intercepts HTTP requests and routes them to the matching WASM runtime └── WASM runtimes ├── virtual-fs Encrypted virtual filesystem (Rust) ├── asgi-bridge Python ASGI/WSGI bridge layer (Rust) ├── wxlsh-parser wxlsh terminal command parser and native commands (Rust) ├── python-bridge Pyodide integration (TypeScript) └── php-bridge php-wasm integration (TypeScript) ``` 这三个 Rust crate 位于 `chall-wasm/` 目录下,由 `pnpm wasm:build` 构建。 ### 请求流程 1. 用户与挑战页面交互,触发对“后端”的 HTTP 请求。 2. Service Worker 拦截请求,并根据挑战配置将其路由到正确的 runtime。 3. Python runtime 或 PHP runtime 处理请求并返回 HTTP 响应。 4. 挑战页面渲染结果。 ### 挑战配置格式 每个挑战都是一个带有 YAML frontmatter 块的 Markdown 文件,用于声明其配置: ``` --- title: Door Is Open # required layout: challenge # selects the challenge UI; without it the page renders as a plain docs page backend: fastapi # required — flask | fastapi | php app: app.py # required — path relative to the challenge's src/ root difficulty: easy category: web packages: [] # extra Python packages to install via micropip tools: [ browser, network, repeater, code ] # omit the field to get exactly these four; # Terminal appears only when you list it, and # browser is added back if you leave it out source_visible: false # true = white-box, false = black-box (default) wasmModule: /challenge/door-is-open/runtime.wasm # produced by keygen --- ``` 挑战源文件通过扫描挑战的 `src/` 目录来获取。出于兼容性考虑,仍然接受传统的 `fs:` 映射,但验证器会发出弃用警告 — 新挑战 SHOULD 依赖于 `src/` 扫描。 ## 部署 构建输出位于 `.vitepress/dist/` 中,作为纯静态文件,可以部署到任何静态托管服务。 ### 构建步骤 ``` # 1. 安装依赖 pnpm install # 2. 完整构建 (WASM + keygen + VitePress) pnpm build ``` ### 部署到 GitHub Pages 本仓库提供了一个可直接使用的工作流文件 [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml) — 直接使用它,而不是自己编写。其结构如下: - **触发器**:推送 `v*` 发布标签,以及手动触发 `workflow_dispatch`。推送到 `main` 分支不会触发部署。 - **Pages 来源**:采用 GitHub Actions 部署方法(`actions/upload-pages-artifact` + `actions/deploy-pages`)。没有 `gh-pages` 分支。 - **工具链**:Node 24 和 SHA 锁定的 `wasm-pack` 0.14.0,与 `release.yml` 匹配。 - **Base path**:构建步骤设置 `SITE_BASE: /wxl-template/`。 `SITE_BASE` 是使项目站点正常工作的关键。VitePress 会将其嵌入到每个资产 URL 中,因此从 `https://.github.io//` 提供服务的站点需要设置 `SITE_BASE: //` — 如果没有它,部署的页面会从域根目录请求资产,导致所有资产都出现 404 错误。仅在部署工作流中设置它;不设置它可使本地和 CI 构建的根目录保持在 `/`。 有两个设置位于 GitHub UI 中,而不是在本仓库中: 1. **Settings → Pages → Source** 必须设置为 **GitHub Actions**。 2. **`github-pages` 环境**必须允许来自 `v*` 标签的部署(除了授权 `workflow_dispatch` 运行的 `main` 分支之外)。如果没有该规则,标签触发的部署作业将被拒绝。 在 fork 之后,运行带有必需标志的 `fork:init` 以重写项目身份: ``` pnpm fork:init --author "" --repo / ``` 它会重写 `package.json` 和 GitHub URL。**它不会修改 `SITE_BASE`。** 该脚本拒绝覆盖现有的 `.github/workflows/deploy.yml` — 而 fork 出来的仓库总是包含该文件 — 因此它会打印 `left untouched` 并继续执行,保留 `SITE_BASE: /wxl-template/` 不变。你需要自己将该值修改为 `//`,否则部署将完全如上所述出现 404 错误。 示例中故意省略了 `--base`:在缺少该标志的情况下,脚本不会修改 `.vitepress/config.mts`,因此环境条件判断 `base: process.env.SITE_BASE ?? '/'` 得以保留,`SITE_BASE` 仍然掌控全局。如果传递 `--base //`,则会用硬编码的字面量替换该行,此后 `SITE_BASE` 将不再影响构建。不要试图使用 `--base none` 来期望获得环境条件判断的形式 — `none` 会直接删除 `base` 声明,因此 VitePress 会回退到 `/`,而 `SITE_BASE` 将变得无效,这正是上述描述的 404 情况。 ### 部署到 Cloudflare Pages 1. 在 Cloudflare Pages 上创建一个新项目并关联 GitHub 仓库。 2. 设置构建命令: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal \ && . "$HOME/.cargo/env" \ && cargo install wasm-pack \ && pnpm install \ && pnpm build 3. 将输出目录设置为 `.vitepress/dist`。 4. 将 `NODE_VERSION=24` 添加到环境变量中。 5. 如果站点是从子路径提供服务的,请添加带有该路径的 `SITE_BASE`(如果是根域名部署,则保持不设置)。 ## 许可证 本项目基于 [Educational Community License, Version 2.0 (ECL-2.0)](LICENSE) 授权。
标签:CTF平台, VitePress, WebAssembly, Web漏洞利用, 可视化界面, 特征检测, 自动化攻击, 静态部署