Zakashaikh/phishing-triage-platform

GitHub: Zakashaikh/phishing-triage-platform

基于规则与机器学习的钓鱼邮件分诊平台,在7095封真实邮件上评估调优,提供CLI和Flask看板双前端。

Stars: 0 | Forks: 0

# 钓鱼邮件分诊平台 [![tests](https://static.pigsec.cn/wp-content/uploads/repos/cas/6b/6b52945adbf8d9e421fe243515ae54cfbd3da263f16b1eabda37cdc0b797b8eb.svg)](https://github.com/Zakashaikh/phishing-triage-platform/actions/workflows/tests.yml) 一个钓鱼邮件检测器,其构建和**衡量方式如同检测工程项目**,而非简单的脚本:包含 15 条可解释的、映射到 MITRE ATT&CK 的启发式规则,基于 **7,095 封真实邮件**进行评估,根据证据进行调优,然后与训练过的 ML 模型进行基准比较 —— 所有这些都封装在一个 CLI 和一个本地分诊看板中。 **主要结果**(留出数据,详情见下文): | 检测器 | Precision | Recall | FP rate | |---|---:|---:|---:| | 手写规则(标记分数 ≥ 20) | 0.89 | 0.51 | 4.7% | | 梯度提升混合模型(规则 + 文本特征) | **0.995** | **1.000** | **0.4%** | ## 看板 将 `.eml` 文件拖放到页面上即可获得分诊视图:分数表、判定结果、带有 ATT&CK 技术标签的危险警告发现、ML 概率、降级处理的 IOC,以及带有高亮欺骗性链接的安全(完全转义)正文预览。 ``` venv\Scripts\python.exe webapp\app.py → http://127.0.0.1:5000 ``` 上传的文件**仅在内存中**进行分析——绝不写入磁盘。分析默认处于离线状态;当配置了 API 密钥时,VirusTotal 按需按请求开启。 ![分诊看板分析伪造的 PayPal 邮件](https://static.pigsec.cn/wp-content/uploads/repos/cas/ed/ed2ed8279e40bcfde19c5fe91b5be61c3de58107289f137363e9de02cce5da38.png) ## 快速开始 ``` python -m venv venv venv\Scripts\python.exe -m pip install -r requirements.txt # 分析单封邮件(仅离线启发式分析) venv\Scripts\python.exe analyser.py sample.eml --no-api # 添加已训练 ML 模型的概率 venv\Scripts\python.exe analyser.py sample.eml --no-api --ml # 批量分拣文件夹 -> 单封邮件 JSON 报告 + summary.csv venv\Scripts\python.exe analyser.py C:\path\to\inbox --output reports # 同时从所有已评分的内容中提取 blocklist-ready IOC 列表 venv\Scripts\python.exe analyser.py C:\path\to\inbox --output reports --extract-iocs # 运行测试套件(102 个测试) venv\Scripts\python.exe -m pytest tests -q ``` 或者使用 Docker(看板地址为 http://localhost:5000): ``` docker build -t phishing-triage . docker run --rm -p 5000:5000 phishing-triage ``` 可选的 VirusTotal 富化:将 `VIRUSTOTAL_API_KEY=...` 放入 `.env` 文件中。如果没有它,分析器的运行方式完全相同,并将 VT 查询标记为已跳过。 CLI 示例输出: ``` SCORE: 55/100 (MALICIOUS) --- FINDINGS --- +10 spf_fail SPF soft-failed [T1672] +15 dkim_fail DKIM signature failed [T1672] +15 dmarc_fail DMARC check failed [T1672] +21 urgency_language Urgency/credential language: urgent, immediately, ... [T1656] --- URLS --- hxxp://www[.]eicar[.]org/download/eicar[.]com FINAL VERDICT: MALICIOUS ``` ## 架构 ``` flowchart LR A[.eml file or bytes] --> B[parse.py
headers, auth results,
URLs + anchor text, IPs] A --> C[attachments.py
SHA-256, type flags] B --> D[scoring.py
15 weighted rules
0-100 score + findings] C --> D D --> E[enrichment.py
optional VirusTotal] E --> F[report.py
console / JSON,
defanged IOCs] D -.-> G[ml.py
trained model
probability] G -.-> F F --> H[CLI analyser.py] F --> I[webapp/ dashboard] ``` | 模块 | 职责 | |---|---| | `parse.py` | `.eml` → 结构化字典:解码的邮件头、SPF/DKIM/DMARC、带锚文本的 URL、公网 IP。遇到格式错误的邮件时绝不崩溃。 | | `attachments.py` | MIME 遍历 → 文件名、类型、大小、SHA-256、危险扩展名 / 宏标记。内容仅进行哈希处理,绝不执行或持久化。 | | `scoring.py` | 检测引擎:每条规则返回一个发现 `(分数, 详情, MITRE id)`;汇总得分 → CLEAN / SUSPICIOUS / MALICIOUS。 | | `enrichment.py` | 可选的 VT 查询(URL、IP、文件哈希),支持优雅降级 —— 每种失败模式都返回带有标签的结果,绝不抛出异常。 | | `ml.py` + `evaluation/` | 特征提取、训练/对比、持久化模型、语料库下载和指标。 | | `report.py` / `analyser.py` / `webapp/` | 报告(双重判定、降级处理的 IOC)以及共享同一个 pipeline 入口点的两个前端。 | ## 评分机制 每条规则都是一个纯函数;报告会准确显示哪些规则被触发及其原因。权重**并非手动设定** —— 而是根据评估语料库进行了调优(见下一节)。 | 规则 | 信号 | 分值 | ATT&CK | |---|---|---:|---| | `spf_fail` | SPF 失败 / 软失败 | 15 / 10 | T1672 | | `dkim_fail` | DKIM 签名失败 | 15 | T1672 | | `dmarc_fail` | DMARC 失败 | 15 | T1672 | | `reply_to_mismatch` | Reply-To 域名 ≠ From 域名 | 5 | T1566.002 | | `return_path_mismatch` | Return-Path 域名 ≠ From 域名 | 0(仅供参考) | T1566.002 | | `brand_freemail` | 品牌显示名,免费邮件发件人 | 25 | T1656 | | `link_text_mismatch` | 锚文本域名 ≠ 真实 href 域名 | 30 | T1566.002 | | `lookalike_domain` | 与已知品牌的编辑距离 ≤ 2 | 25 | T1583.001 | | `punycode_domain` | IDN/punycode (`xn--`) 域名 | 15 | T1583.001 | | `raw_ip_url` | URL 主机是字面量 IP | 20 | T1566.002 | | `url_shortener` | 已知的短链接隐藏目的地 | 10 | T1566.002 | | `suspicious_tld` | 高滥用 TLD (.top, .zip, …) | 10 | T1583.001 | | `urgency_language` | 紧急性/凭据关键词 | 每个 7 分,上限 21 分 | T1656 | | `dangerous_attachment` | 可执行/脚本附件,双重扩展名 | 30 | T1566.001 | | `macro_attachment` | 启用宏的 Office 文档 | 25 | T1566.001 | 判定标准:得分 ≥ 50 → **MALICIOUS**,≥ 20 → **SUSPICIOUS**,否则为 **CLEAN**。任何 VirusTotal 命中(`malicious > 0`)都会无视分数直接覆盖为 MALICIOUS。 ## 验证的认证 (`--verify-auth`) 默认情况下,解析器会从邮件自身的 `Received-SPF` 和 `Authentication-Results` 头部读取 SPF/DKIM/DMARC —— 但这些头部位于邮件*内部*,因此发件人可以轻松地在自己的邮件中写入 `dkim=pass`。这里存在一个信任边界,而 `--verify-auth` 通过实际计算结果而非盲目相信来消除这个隐患 ([auth.py](auth.py)): - **DKIM** —— 使用从 DNS (`._domainkey.`) 获取的公钥以加密方式验证签名。 - **SPF** —— 根据将邮件传递给接收 MTA 的 IP(`Received` 链中最顶部的公网 IP —— 这是攻击者无法伪造的头部)来评估发件人域名的已发布策略。 - **DMARC** —— 实时获取 From 域名的 `_dmarc` 策略,并针对 DKIM `d=` 域名和 SPF 信封域名进行宽松的标识符对齐。 验证后的结果会覆盖报告中的原始结果;任何无法解析的内容(无网络、域名失效)都会降级回退为邮件头的值,因此离线行为保持不变。报告会同时显示两者,并且第 16 条规则将该特性投入使用:当 `Authentication-Results` 声明为 `dkim=pass` 但签名在加密验证下失败时,`auth_header_forged` (+25, T1036) 就会被触发 —— 这说明邮件头本身就是伪造的。这条规则刻意**不**包含在 ML 特征布局中,以使其保持在已发布模型训练时所用的 15 列特征不变。 ``` venv\Scripts\python.exe analyser.py suspicious.eml --no-api --verify-auth ``` ## 评估:先测量,后调优 该检测器在 **7,095 封已标记的真实邮件**上进行了测试 —— 包括 2,945 封钓鱼邮件(Nazario 语料库,2005–2023 年)和 4,150 封合法邮件(SpamAssassin 的 easy/hard ham)。完整的方法论、表格和误差分析见:[evaluation/RESULTS.md](evaluation/RESULTS.md)。 测量按类别划分的各规则触发率后,发现有两条规则在**合法邮件上的触发率高于**钓鱼邮件(`return_path_mismatch`:在 ham 中占 71%,而在钓鱼邮件中占 25% —— 因为邮件列表和新闻通讯合法地使用不同的退信域名)。基于数据的重新加权: | 工作点 | 调优前 | 调优后 | |---|---|---| | Precision(在 SUSPICIOUS 时标记) | 0.63 | **0.91** | | 假阳性率 | 20.6% | **3.7%** | | Recall | 0.50 | 0.51 | ![得分分布](https://static.pigsec.cn/wp-content/uploads/repos/cas/64/64ee2657fecfda8413137ac582ecf10591b6df051b04926fcbc855970f03d26e.png) ![阈值扫描](https://static.pigsec.cn/wp-content/uploads/repos/cas/48/48420cc9c5d00287993d8b5141515f973291d300416d8fdab3417df7f78bcd1e.png) **为什么 Recall 0.51 是正确的权衡,而不是缺陷。** 这是一个*分诊*工具:它的输出是分析师的待处理队列,而分析师的注意力是稀缺资源。在 20.6% 的假阳性率下(未调优的检测器),三分之一的警报都是噪音,队列会被忽视 —— 这是典型的警报疲劳失败。调优后的工作点以牺牲 Recall 为代价换取了 3.7% 的 FP 率,从而确保当工具标记某事时,它是值得分析师花时间去看的。漏报主要集中在没有 URL 和认证头部的纯文本钓鱼邮件上 —— 规则毫无切入点 —— 这正是 ML 层所填补的空白(在留出数据上的 Recall 为 1.000)。为队列提供 Precision 优先的启发式规则,为长尾提供 ML 模型。 ## 规则对比机器学习 四种配置,采用相同的分层 80/20 划分,仅在 1,417 封留出邮件上进行评判(TF-IDF 仅在训练文本上进行拟合 —— 无数据泄露): | 配置 | Precision | Recall | F1 | FP rate | ROC AUC | |---|---:|---:|---:|---:|---:| | 仅规则(基线) | 0.886 | 0.514 | 0.650 | 4.7% | — | | 逻辑回归(文本,无规则) | 0.990 | 0.986 | 0.988 | 0.7% | 0.998 | | 逻辑回归(混合) | 0.985 | 0.981 | 0.983 | 1.1% | 0.997 | | **梯度提升(混合)** —— 集成在 `--ml` 中 | **0.995** | **1.000** | **0.997** | **0.4%** | 1.000 | ![ROC 曲线](https://static.pigsec.cn/wp-content/uploads/repos/cas/61/612d76510579969b8b010da54ec904f532a1127bbcb903ed4eeaa70856bb21d8.png) **为什么 ML 在这里能胜出:** 规则的盲区是那些没有认证头部也没有 URL 欺骗手法的纯文本钓鱼邮件 —— 没有任何能触发的特征。这些消息中的*文字*具有高度区分度,TF-IDF 将这个信号传递给了模型,同时也修复了将营销新闻通讯误判为钓鱼邮件的假阳性问题。 **诚恳的警示**(完整版本在 RESULTS.md 中):1.000 的 AUC 在一定程度上反映了 2003 年的 ham 与 2005–2023 年的钓鱼语料库之间的*巨大差异* —— 模型在学习钓鱼特征的同时也学习了语料库的特征。客观的说法应该是“在此语料库上实现了近乎完美的分离”,而不是“在生产环境中也能达到 99.7%”。规则依然有其存在的价值:具有可解释性、无需训练数据,并且它们能覆盖此语料库无法教会模型的信号(SPF/DKIM、punycode)。 ## 从判定到行动:IOC 提取 仅仅一个判定结果并不能驱动 SOC 的下一步操作 —— 阻断列表和监视列表才能做到这一点。`--extract-iocs` 会将每封被评为 **SUSPICIOUS 或 MALICIOUS** 的邮件中的所有 URL、域名、IP、附件 SHA-256 以及发件人/回复地址提取出来,聚合到一个去重后的 `iocs.csv` 文件中: ``` type,value,defanged,source,verdict url,http://paypa1.com/login,hxxp://paypa1[.]com/login,lookalike.eml,MALICIOUS domain,paypa1.com,paypa1[.]com,lookalike.eml,MALICIOUS sha256,e0254d4e2d27...,e0254d4e2d27...,attachment.eml,SUSPICIOUS reply_to,refunds@mail.ru,refunds@mail[.]ru,spoofed.eml,MALICIOUS ``` 在 SOC 中至关重要的设计选择: - **干净的邮件不会贡献任何内容** —— 从良性邮件中收集 IOC 会用合法域名污染阻断列表。 - **原始数据与降级处理数据并列** —— 原始值供机器使用(邮件网关阻断列表、EDR 监视列表、SIEM 查询),降级处理的副本可以安全地粘贴到工单和聊天记录中。 - **`source` 可将每个 IOC 追溯到其对应的邮件**,因此在采取行动之前可以对条目进行审计。 典型循环:批量分诊收件箱 → 审查 `summary.csv` → 将 `iocs.csv` 条目推送到邮件网关 / DNS 阻断列表 → 附上 JSON 报告升级处理原始的 MALICIOUS 邮件。 ## 在现代样本上的验证 上述语料库较为陈旧 —— 其中的 ham 来自 ~2003 年,且大部分钓鱼邮件早于 SPF/DKIM/DMARC 的出现,因此认证规则在其中几乎不会被触发(详见 RESULTS.md)。为了在实际带有 `Authentication-Results` 头部的邮件上进行验证,第二条评估路径提取了**整个 [phishing_pot](https://github.com/rf-peixoto/phishing_pot)料库 —— 由蜜罐收集的 8,614 封真实钓鱼邮件**(收件人已被维护者匿名化处理): ``` python evaluation/download_live.py --all # one tarball -> corpus/live.zip (AES, gitignored) python evaluation/validate_modern.py # -> evaluation/MODERN_VALIDATION.md ``` 在所有 **8,585 个可解析样本**中测量的结果([evaluation/MODERN_VALIDATION.md](evaluation/MODERN_VALIDATION.md) 包含按规则划分的触发率;[CASE_STUDIES.md](evaluation/CASE_STUDIES.md) 保留了最新 30 封邮件的逐邮件详情): | 检测器 | 在现代钓鱼邮件上的 Recall | |---|---| | Rules ≥ SUSPICIOUS | 2,071/8,585 (**24.1%**) —— 高 Precision 的工作点保持安静,正如设计预期 | | ML 模型, p ≥ 0.5 | 8,272/8,585 (**96.4%**) —— 对完全不在其训练数据范围内的来源和时代表现出极强的泛化能力 | | Rules OR ML (triage union) | 8,337/8,585 (**97.1%**) | 规模化带来了两个诚实教训,这是仅靠 30 个样本的检查所得不到的:早先仅对最新 30 个样本运行时,ML 的得分为 30/30 —— 在样本量达到 8,585 时,得分稳定在 96.4%,这才是值得引用的数据。此外,现代钓鱼邮件很大程度上能够绕过静态启发式规则 (24.1%):SPF/DKIM/DMARC 规则只在四分之一的样本上被触发,但大多数现代诱饵对于仅基于规则的检测器来说太干净了 —— 这正是 ML 层所填补的空白。一如既往的警示是:该语料库全部都是钓鱼邮件,因此它仅测量了 Recall;在*现代合法*邮件上的假阳性率在这里并未得到评估。 生成器可处理任何存放 `.eml` 文件或 AES 加密 zip 压缩包(例如你自己导出的垃圾邮件)的文件夹,且其输出内容可以安全地提交:收件人地址会被打码,URL 会被降维处理 (`hxxp://evil[.]example`)。 ## JSON 报告 Schema 每次分析都会生成 `_report.json`: ``` { "email": { "from_": "...", "from_domain": "...", "subject": "...", "spf": "fail", "dkim": "fail", "dmarc": "fail", "has_html": true, ... }, "urls": ["http://..."], // raw IOCs (console output is defanged) "ips": ["203.0.113.5"], "attachments": [{ "filename": "...", "sha256": "...", "is_dangerous_ext": true, ... }], "findings": [{ "rule_id": "spf_fail", "points": 15, "detail": "...", "mitre": "T1672" }], "score": 55, "heuristic_verdict": "MALICIOUS", // from the rules alone "final_verdict": "MALICIOUS", // after VT override / ML combination "ml": { "probability": 0.999, "verdict": "MALICIOUS", "config": "histgbm_hybrid" }, "enrichment": { "urls": [...], "ips": [...], "files": [...] }, "parse_errors": [], "body_preview": "first 2000 chars of body text" } ``` ## 部署与 SOC 集成 在 SOC 中,钓鱼邮件分诊处于 pipeline 中,而不是桌面上。该工具暴露了 pipeline 所需的两个集成接口: ``` flowchart LR A["User 'Report Phishing' button
or mail-gateway journaling"] -->|.eml| B["SOAR playbook
(XSOAR, Splunk SOAR, TheHive)
or watched-folder cron"] B -->|"POST /analyse (.eml upload)"| C["Triage platform
Flask API"] B -->|"analyser.py folder/ --extract-iocs"| D["Triage platform
CLI batch"] C -->|JSON report| E["SIEM ingestion
(Splunk / Elastic)"] D -->|"per-email JSON + summary.csv"| E D -->|iocs.csv| F["Blocklists & watchlists
(mail gateway, EDR, TI platform)"] E -->|"verdict=MALICIOUS"| G["Escalated ticket with
findings + ATT&CK IDs"] ``` - **REST**:带有 `.eml` 上传请求的 `POST /analyse` 会返回完整的 JSON 报告 —— SOAR 的富化操作仅需一次 HTTP 调用。 - **CLI 批处理**:将 `analyser.py` 指向一个文件夹(例如网关转存日志邮件的地方) —— 每封邮件的 JSON 报告用于 SIEM 文件摄取,`summary.csv` 用于队列管理,`iocs.csv` 用于阻断列表。 - **机器可读的输出**:下方的 JSON schema 稳定且有文档记录;发现结果包含 MITRE ATT&CK ID,因此工单和 SIEM 看板可以基于技术进行数据透视。 实际的生产环境缺口(属于刻意限制的范围,将是下一步计划):API 的认证和速率限制、用于应对突发流量的队列,以及原生的 syslog/HEC 发送以替代文件投递摄取。 ## 仓库结构与防病毒软件说明 ``` analyser.py parse.py scoring.py attachments.py enrichment.py report.py ml.py webapp/ Flask dashboard evaluation/ corpus download, metrics, training, RESULTS.md models/ trained model (joblib) tests/ 81 pytest tests + crafted .eml fixtures docs/ charts used in this README corpus/ (gitignored, created by evaluation/download_corpus.py) ``` 真实的钓鱼样本一旦接触到磁盘就会触发防病毒软件 —— 在开发过程中,Windows Defender 在语料库下载到一半时将其隔离。因此,下载器会在**内存中**拆分邮箱,并将样本存储在 **AES 加密的 zip** 中(密码为 `infected`,这是恶意软件研究的惯例),并且评估器会在不写入任何未加密样本的情况下将它们流式传输回内存。重现整个评估过程: ``` venv\Scripts\python.exe evaluation\download_corpus.py venv\Scripts\python.exe evaluation\evaluate.py venv\Scripts\python.exe evaluation\train.py ``` ## 未来工作 - **实时 IMAP 分诊** —— 将 pipeline 指向真实的收件箱,并自动为传入的邮件打标签。 - **现代同源语料库** —— 在来自相同邮件流的当代钓鱼/合法邮件上重新评估,以获取贴近生产环境的真实数据。 - **LLM 正文分析** —— 一个可选的针对正文本本的社会工程学分析阶段。 - **WHOIS/域名年龄富化** —— 新注册域名是一个强烈的信号,而当前规则无法察觉。
标签:AMSI绕过, Apex, Flask, 威胁检测, 安全规则引擎, 机器学习, 请求拦截, 逆向工具, 钓鱼邮件分析