DreadpiratePickles/phish-pond
GitHub: DreadpiratePickles/phish-pond
一个基于同意的内部钓鱼意识模拟器,帮助安全团队开展防御性演练并以独立收件人维度衡量打开率和点击率等培训效果指标。
Stars: 0 | Forks: 0
# 🎣 Phish Pond
### 授权的、基于同意的钓鱼意识模拟器 — 受到人性化约束。
    
*目的不是抓住人。而是教他们 —— 然后证明这有效。*
Phish Pond 运行*内部*钓鱼意识演练并跟踪结果,以便安全团队可以进行衡量和改进,而不仅仅依靠感觉。它创建活动,渲染刻意无害的邮件模板,记录针对每个收件人 token 的打开和点击,并将用户引导至辅导页面 —— 它**从不**收集凭据、冒充品牌,或针对任何你不拥有或未签约测试的人。它是共享外观和感觉的防御性安全套件中的十个工具之一。在底层,它有意设计得小巧而单调:SQLite、标准库 SMTP 发送器、微型 Flask 应用,以及 `rich` CLI。
## 为什么会有它
截图和“我认为人们现在更小心了”并不是指标。Phish Pond 在 SQLite 中记录收件人和事件(`sent` / `opened` / `clicked`),因此你可以报告每个活动的打开率/点击率,跟进需要辅导的个人,并显示随时间变化的趋势 —— 默认进行试运行,因此没有人会意外地将邮件服务器变成制造混乱的加农炮。
## 工作原理(数据流)
```
create-campaign ─▶ campaigns table
add-recipient ─▶ recipients table (unique uuid4 token per person)
import-recipients (CSV, validated, size-capped)
send-campaign ──▶ render_email(template, recipient, base_url)
├─ template path is validated (no traversal)
├─ display_name is HTML-escaped
└─ injects: /t/
.gif and /c/
dry-run (default): prints the message, sends nothing
--authorized-training + --allow-domain: delivers over SMTP,
records a 'sent' event per delivered message
serve (Flask) ──▶ GET /t/.gif → records 'opened', returns 1x1 pixel
GET /c/ → records 'clicked', 302 → /training
GET / → dashboard (open/click rates)
GET /training → coaching page
GET /healthz → {"status":"ok"}
stats / report ─▶ aggregate events into per-campaign and per-recipient views
```
**指标按*独立收件人*计算,而不是按原始事件计算。** 邮件客户端预取五次的跟踪像素仍然只是一个打开它的人 —— 因此打开率和点击率始终保持在 0–100% 之间。
### 数据模型
| 表 | 列 |
| --- | --- |
| `campaigns` | `id`, `name`, `subject`, `template`, `created_at` |
| `recipients` | `id`, `campaign_id` (FK), `email`, `display_name`, `token` (unique), `created_at` |
| `events` | `id`, `recipient_id` (FK), `event_type` (`sent`/`opened`/`clicked`), `user_agent`, `ip_address`, `created_at` |
### HTTP endpoint
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| `GET` | `/` | 活动 dashboard。 |
| `GET` | `/t/.gif` | 跟踪像素 —— 记录 `opened` 事件。 |
| `GET` | `/c/` | 记录 `clicked` 事件,重定向至 `/training`。 |
| `GET` | `/training` | 意识辅导页面。 |
| `GET` | `/healthz` | 存活探针。 |
未知或格式错误的 token 会被忽略(像素仍会返回 200,因此有效的 token 不会被区分出来),并且每个响应都带有 `X-Content-Type-Options`、`X-Frame-Options: DENY` 和 `Referrer-Policy: no-referrer`。
## 设置
Python 3.11+(已在 3.13 上测试)。在项目文件夹中:
```
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
pip install -e . # optional: gives you the `phish-pond` command
cp .env.example .env
```
从源码运行不需要可编辑安装 —— 设置 `PYTHONPATH=src` 并直接调用该模块:
```
PYTHONPATH=src python -m phish_pond.cli init-db
```
以下示例使用简写形式 `phish-pond`;如果你跳过了 `pip install -e .`,请替换为
`PYTHONPATH=src python -m phish_pond.cli`。配置从 flag 或环境变量中读取(参见 `.env.example`):
`PHISH_POND_DB`、`PHISH_POND_BASE_URL`、`PHISH_POND_SMTP_HOST/PORT/USER/PASSWORD`、
`PHISH_POND_FROM`、`PHISH_POND_ALLOWED_DOMAINS`(逗号分隔的白名单)。
## CLI
每个命令都会打印一个紧凑的品牌化标头,使用 `rich` 渲染结果,并支持两个全局 flag:
- `--json` — 向 stdout 输出纯 JSON,无修饰(用于脚本 / `jq`)。
- `--plain` / `--no-color` — 无样式文本。同样支持 `NO_COLOR`,并在输出不是 TTY 时自动降级。JSON 始终是纯净的。
### `init-db`
创建 SQLite 表(在首次写入时也会延迟触发)。
```
phish-pond init-db
```
### `create-campaign`
```
phish-pond create-campaign --name "Q3 Awareness" --subject "Invoice needs review"
# 已创建 campaign #1:Q3 Awareness
```
### `add-recipient`
在插入之前验证电子邮件并确认活动存在。
```
phish-pond add-recipient --campaign-id 1 --email analyst@corp.example.com --name "Analyst"
```
### `import-recipients`
从包含 `email` 和 `name` 列的 CSV 批量加载名单(标头不区分大小写;也接受 `display_name`)。无效行将被跳过并报告 —— 一个拼写错误永远不会影响整批数据。超过 5 MB 的文件将被拒绝。
```
phish-pond import-recipients --campaign-id 1 --csv team.csv
```
```
Imported 2 recipient(s) into campaign #1.
Skipped rows
┌──────┬────────┬───────────────────────────────────────┐
│ Line │ Email │ Reason │
├──────┼────────┼───────────────────────────────────────┤
│ 4 │ broken │ 'broken' is not a valid email address │
└──────┴────────┴───────────────────────────────────────┘
```
### `send-campaign`
渲染活动(试运行,即默认值)或通过 SMTP 投递。**真实投递需要 `--authorized-training` *以及*收件人域名白名单**(`--allow-domain`,可重复,或 `PHISH_POND_ALLOWED_DOMAINS`)。白名单之外的收件人将被跳过并报告。`--rate-limit` 限制中继速度;`--max-recipients` 限制群发上限;`--no-starttls` 仅用于受信任的本地测试中继。
```
# 默认安全:渲染所有内容,不发送任何内容。
phish-pond send-campaign --campaign-id 1
# 仅允许向自有域名进行授权的实时发送。
phish-pond send-campaign --campaign-id 1 --authorized-training \
--allow-domain corp.example.com \
--smtp-host relay.internal --from-address security-training@corp.example.com
```
```
╭─ Send summary ───────────────────────────────────────╮
│ 12 messages delivered │
│ 1 recipients skipped (domain not allowed: gmail.com) │
╰──────────────────────────────────────────────────────╯
```
### `stats`
每个活动的汇总。点击率被颜色编码为辅导信号(绿色 < 10% < 黄色 < 30% ≤ 红色)。添加 `--campaign-id` 以专注于某一个。
```
phish-pond stats
phish-pond stats --json | jq '.[0].clicked'
```
```
Campaign telemetry
┌───┬───────────────┬────────────┬──────┬────────┬─────────┬────────┬─────────┐
│ # │ Campaign │ Recipients │ Sent │ Opened │ Clicked │ Open % │ Click % │
├───┼───────────────┼────────────┼──────┼────────┼─────────┼────────┼─────────┤
│ 1 │ Q3 Awareness │ 2 │ 2 │ 2 │ 1 │ 100% │ 50% │
└───┴───────────────┴────────────┴──────┴────────┴─────────┴────────┴─────────┘
```
### `report`
用于培训跟进的某一活动下每个收件人的参与情况(谁在何时打开/点击)。
```
phish-pond report --campaign-id 1
```
```
#1 Q3 Awareness — recipient report
┌──────────────────────────┬─────────┬────────┬─────────┬───────────────────────────┐
│ Email │ Name │ Opened │ Clicked │ Clicked at │
├──────────────────────────┼─────────┼────────┼─────────┼───────────────────────────┤
│ analyst@corp.example.com │ Analyst │ ✓ │ ✓ │ 2026-07-05T11:54:11+00:00 │
│ lead@corp.example.com │ Lead │ ✓ │ – │ │
└──────────────────────────┴─────────┴────────┴─────────┴───────────────────────────┘
```
### `list-campaigns` / `list-templates`
```
phish-pond list-campaigns
phish-pond list-templates
```
### `serve`
运行 Flask dashboard 和跟踪 endpoint。**默认绑定 `127.0.0.1`。**
```
phish-pond serve --host 127.0.0.1 --port 5062
```
绑定到非环回主机将打印警告(你正在向网络公开跟踪 + dashboard),而 `--debug` 会打印醒目的警告,因为 Flask 的 debugger 是一个交互式的 RCE 攻击面 —— 切勿将其公开暴露。
对于本地 SMTP 测试(无 TLS),请使用调试服务器和 `--no-starttls`:
```
python -m smtpd -c DebuggingServer -n localhost:1025
```
## 安全与授权
这是一个**防御性**工具,用于你拥有或明确签约测试的系统和人员。它的构建旨在让错误的事情变得困难:
- **试运行是默认设置。** 没有 `--authorized-training` 就不会投递任何内容。
- **仅限授权目标门控。** 除非你提供收件人域名白名单,否则将拒绝实时发送;范围外的地址将被跳过,而不是被发送。
- **绝不收集凭据。** 链接指向辅导页面。电子邮件带有 `X-Phish-Pond: authorized-awareness-simulation` 标头,因此原始消息不会被误认为是真正的攻击。
- **恶意输入处理。** 电子邮件地址经过验证(拒绝标头注入),模板名称针对路径遍历进行了验证,显示名称经过了 HTML 转义,并且 CSV 名单有大小限制。
- **输出中没有机密。** SMTP 密码来自环境变量/flag,并且永远不会被记录。首选 `PHISH_POND_SMTP_PASSWORD` 而不是 `--smtp-password`(进程列表是公开的)。
- **安全的 Web 默认设置。** 环回绑定、安全标头、token 格式验证,且无反射性重定向。
在进行任何实时活动之前,请获得书面授权。辅导,而不是羞辱 —— 报告视图用于跟进,而不是排行榜。
## 测试
```
PYTHONPATH=src python -m pytest -q
```
快速且轻依赖 —— 无网络,无 sleep。覆盖率包括独立收件人指标计数、电子邮件验证、模板路径遍历拒绝、HTML 转义、CSV 导入(添加/跳过/超大)、每个收件人的报告、发送授权门控,以及 JSON 输出纯度。
```
---
## 🏷️ 为什么叫 "Phish Pond"?
A pond you fish in deliberately, with permission, having told everyone the pond exists. Consent-based and humanely bounded: no shaming, no dark patterns, no "gotcha" screenshots for the all-hands. And the metrics are per-recipient and trustworthy, which matters more than it sounds — a click-rate that counts your mail scanner's automated fetch as a human being fooled is worse than having no number at all, because you'll act on it.
---
## 🔬 这个工具是如何构建的
> One of ten defensive tools rebuilt to a single standard — *understand, reimagine,
> build the CLI, harden, document, verify*. The goal was never to change what these
> tools **are**, but to take each one as far as it could honestly go on correctness,
> safety and the experience of the person actually running it at 2am.
**Purpose.** A humane, hard-to-misuse internal phishing-awareness simulator.
Dry-run by default, authorized-targets-only for live sends, with trustworthy
per-recipient and per-campaign metrics. It never collects credentials or
impersonates a brand — links lead to a coaching page.
**How it works.** Campaigns and recipients live in SQLite; each recipient gets a
unique token. `send-campaign` renders a benign template (dry-run prints it, sends
nothing). A tiny Flask app records `opened` events from a tracking pixel and
`clicked` events from a redirect, then aggregates them into per-campaign and
per-recipient reports.
**Standout CLI.** Color-coded campaign telemetry table (click-rate green/yellow/
red as a coaching signal), a per-recipient report with ✓/– marks and first-click
timestamps, `list-templates` as a rich tree, a live-send consent panel and
send-summary panel (delivered/skipped/blocked), and CSV import with a skipped-row
report.
**Key improvements.** *Functional:* fixed the headline metric bug — open/click
rates now count **distinct recipients**, so a prefetched pixel can no longer yield
a >100% rate; added `import-recipients` (validated, 5 MB-capped CSV), a
per-recipient `report`, and clean "Campaign #N not found" errors. *Security:*
template path-traversal guard (bare filename resolved strictly inside
`email_templates/`), email validation that rejects control chars/newlines
(closing an SMTP header-injection vector), HTML-escaped display names, and the
authorized-domain allowlist gate on live sends; the tracking pixel returns 200
even for unknown tokens so valid tokens aren't distinguishable.
**Example.**
```bash
PYTHONPATH=src python -m phish_pond.cli report --campaign-id 1
```
## ⚖️ 授权使用与安全宪章
**这些是防御性工具,用于你拥有或被明确授权评估的系统、文件、网络和人员。** 在运行任何东西之前,请阅读以下内容:
- **授权不是可选的。** 钓鱼模拟、网络扫描、IP 声誉查询和 Web 应用探测都会触及他人的系统或
数据。请先获得书面授权。在你声明之前,有几个工具*拒绝采取行动*
(`--yes`、`--authorized-training`、`--i-have-authorization`、
`--i-am-authorized` 等)。
- **默认安全。** 每个 Web UI 都绑定到 `127.0.0.1`(环回)。出口流量、
实时发送和主动扫描都由显式 flag 控制 —— 试运行、
被动或拒绝并警告始终是默认设置。
- **没有意外的“自毁”操作。** Flask `--debug`(Werkzeug 交互式
debugger 在任何 traceback 上都是远程代码执行)受到强烈警告,并且
在环回地址之外完全被拒绝。不受信任的输入有大小限制、经过验证,并且
被无害化渲染,因此恶意文件或日志行不会崩溃 —— 或接管 ——
你的终端。
- **输出中没有机密。** API key 保留在请求标头中,密码来自
环境,并且不会记录或打印任何敏感信息。
这些不是攻击性工具。它们不包含任何 exploit、凭据
收集器或 payload。如果某个工具*可能*被滥用,那么它的结构设计就会
抵御这种滥用。
## 🧪 开发
```
python -m venv .venv && source .venv/bin/activate
pip install -e .
pytest -q # 26 tests, no network, no sleeps
phish-pond --help
```
## 📄 许可证
在 **MIT License** 下发布。参见 [LICENSE](LICENSE)。
防御性工具。没有 exploit,没有 payload,没有凭据收集器。
标签:Flask, Python, SQLite, 内部安全工具, 安全度量, 无后门, 逆向工具