yaswanthmaddula/ThreatLens

GitHub: yaswanthmaddula/ThreatLens

基于随机森林的恶意 URL 检测平台,通过纯本地特征工程实现毫秒级、可解释的钓鱼链接识别。

Stars: 0 | Forks: 0

# ThreatLens ### 基于 AI 的 URL 威胁情报平台 **利用机器学习在毫秒级检测恶意 URL —— 没有黑盒,没有猜测。** [在线演示](https://threatlens-ai-powered-url-security.onrender.com) · [API 文档](#api-documentation) · [ML Pipeline](#machine-learning-pipeline) · [架构](#system-architecture)
## 执行摘要 ### 问题所在 钓鱼攻击和恶意 URL 是凭证窃取、恶意软件投递和金融诈骗的主要途径。2024 年,每天发送的钓鱼邮件超过 34 亿封。传统防御手段显得捉襟见肘: - **黑名单** 是被动的 —— 它们只能捕获已被报告为恶意的 URL - **人工审核** 无法扩展 —— 人类无法每秒检查数百万个 URL - **简单启发式规则** 很容易绕过 —— 攻击者可以通过变换字符规避关键词过滤 - **信誉查询服务** 依赖网络调用 —— 速度慢、成本高,且对于全新域名不可用 ### ThreatLens 如何解决 ThreatLens 直接分析 **URL 自身的结构** —— 无需网络调用、无需黑名单查询、没有外部依赖。训练好的 Random Forest 分类器通过纯 Python 检查从 URL 字符串中提取的 28 个工程化特征,在一秒内返回判定结果。每一个预测都附带了通俗易懂的说明,准确指出是哪些信号驱动了该决策。 ## 核心功能 | 功能 | 实现 | 详情 | |---|---|---| | **AI 威胁检测** | `RandomForestClassifier` (300 棵树) | 在 235,795 个 URL 上训练;在留出测试集上准确率达 99.4% | | **28 项信号特征提取** | `feature_extraction.py` | 熵、长度、字符比率、启发式规则 —— 纯 Python 实现,零网络 I/O | | **可解释的预测** | `app.py` 中的 `_generate_explanations()` | 基于规则的引擎将特征值映射为人类可读的发现 | | **风险评分** | `predict_proba()` → 校准阈值 | 返回 `P(malicious)` 作为 0–1 的风险评分 | | **三级分类** | `_run_prediction()` 中的阈值逻辑 | 安全 / 可疑 (≥0.70) / 恶意 (≥0.90) | | **扫描历史** | 以 `threatlens_history` 为键的 `localStorage` | 客户端持久化保存最近 10 次扫描;支持一键重新扫描 | | **动画扫描进度** | `script.js` 中的 5 步进度 UI | 与 API 响应生命周期同步的顺序步骤指示器 | | **速率限制** | `flask-limiter` | `/api/v1/predict` 限制为 60 次请求/分钟;全局默认 200 次请求/天 | | **CORS 支持** | `flask-cors` | 可通过 `ALLOWED_ORIGINS` 环境变量配置 | | **Docker 部署** | 多阶段 `Dockerfile` + `docker-compose.yml` | 非 root 用户,Gunicorn (2 个 worker),Nginx 反向代理 | | **健康检查端点** | `GET /health` | 根据模型加载状态返回 200 OK 或 503 降级 | | **模型指标 API** | `GET /api/v1/metrics` | 从 `model_metrics.json` 提供实时的准确率、F1、ROC-AUC、混淆矩阵 | | **响应式 UI** | 原生 HTML/CSS/JS | 移动优先布局;在 1024px, 768px, 640px 处设置断点 | | **无障碍设计** | ARIA 角色、实时区域、键盘导航 | `aria-live`、`aria-label`、`role="search"`,完整的键盘支持 | ## 截图 **登录页面**ThreatLens-—-AI-Powered-URL-Threat-Detection **安全链接的威胁分析结果** ThreatLens-—-AI-Powered-URL-Threat-Detection (1) **风险链接的威胁分析结果**ThreatLens-—-AI-Powered-URL-Threat-Detection (2) ## 系统架构 ``` graph TB subgraph Client ["Browser — Vanilla JS"] UI["index.html\nLanding + Scanner + Results"] JS["script.js\nFetch API · localStorage · DOM"] CSS["style.css\nCSS custom properties · Responsive"] end subgraph Proxy ["Nginx — Alpine"] NGINX["nginx.conf\nStatic file serving\n/api/ → proxy_pass backend:8000"] end subgraph API ["Flask API — Python 3.12"] APP["app.py\nRoutes · Rate limiter · CORS\n_run_prediction()"] FEAT["feature_extraction.py\nextract_features(url)\n28 signals, zero network I/O"] CFG["config.py\nEnvironment-driven config"] end subgraph ML ["ML Layer"] MODEL["url_model.pkl\nRandomForestClassifier\n300 trees · 28 features"] METRICS["model_metrics.json\nAccuracy · F1 · ROC-AUC\nConfusion matrix"] end subgraph Data ["Training Data"] CSV["phishing_urls.csv\n235,795 URLs\nlabel: 0=malicious, 1=safe"] TRAIN["train_model.py\nStratified split · 5-fold CV\nFeature importance"] end UI -->|"POST /api/v1/predict\n{url: string}"| NGINX NGINX -->|proxy_pass| APP APP --> FEAT FEAT --> APP APP -->|"predict_proba()"| MODEL MODEL -->|"[P(malicious), P(safe)]"| APP APP -->|"JSON response"| NGINX NGINX -->|response| UI CSV --> TRAIN TRAIN -->|joblib.dump| MODEL TRAIN -->|json.dump| METRICS APP -.->|startup load| MODEL APP -.->|startup load| METRICS ``` ### 组件职责 | 组件 | 语言 | 职责 | |---|---|---| | `frontend/index.html` | HTML5 | 单页布局:Hero 区、扫描器、结果、历史记录 | | `frontend/script.js` | ES2022 JavaScript | API 调用、DOM 操作、localStorage、进度动画 | | `frontend/style.css` | CSS3 | 通过自定义属性构建设计系统、响应式网格、动画 | | `backend/app.py` | Python 3.12 | Flask 路由、预测编排、说明生成、速率限制 | | `backend/feature_extraction.py` | Python 3.12 | 28 项信号的 URL 特征工程,零外部依赖 | | `backend/train_model.py` | Python 3.12 | 模型训练、分层划分、交叉验证、指标导出 | | `backend/config.py` | Python 3.12 | 通过 `python-dotenv` 实现的集中式环境变量配置 | | `nginx.conf` | Nginx | 静态文件服务、`/api/` 反向代理、安全响应头 | | `Dockerfile` | Docker | 多阶段构建、非 root 运行时用户、Gunicorn 入口点 | | `docker-compose.yml` | Docker Compose | 后端 + 前端服务、健康检查、数据卷挂载 | ## URL 分析工作流 ``` flowchart TD A([User enters URL]) --> B{URL valid?} B -- No --> C[Highlight input field red\n1.2s error state] B -- Yes --> D[Start 5-step progress animation] D --> E["POST /api/v1/predict\n{url: string}"] E --> F[Flask: input validation\nLength ≤ 2048 · strip whitespace] F --> G["extract_features(url)\n28 signals extracted"] G --> H[Build feature vector\nAlign to training column order] H --> I["model.predict_proba(df)\nRandomForest: 300 trees vote"] I --> J["P(malicious) = proba[class_list.index(0)]"] J --> K{Threshold check} K -- ">= 0.90" --> L[Malicious · Critical] K -- ">= 0.70" --> M[Suspicious · Medium] K -- "< 0.70" --> N[Safe · Low] L --> O[_generate_explanations\nRule-based signal mapping] M --> O N --> O O --> P["JSON response\nprediction · risk_score · risk_level\nexplanations · features"] P --> Q[Frontend: renderResult\nCircular progress ring\nVerdict badge · Meta pills] Q --> R[renderFeatures\n28 feature cards with bar indicators] Q --> S[Explanation list\nColor-coded positive/negative signals] Q --> T[addToHistory\nlocalStorage persists last 10 scans] ``` ## 机器学习 Pipeline ### 训练 (`train_model.py`) 该模型完全基于在推理阶段可从 URL 字符串中推导出的特征进行训练。这是一项经过深思熟虑的架构决策,旨在消除通常困扰 URL 分类系统的训练/推理特征不匹配问题。 ``` Dataset: phishing_urls.csv (235,795 URLs) label=0 → 100,945 malicious (42.8%) label=1 → 134,850 safe (57.2%) Split: 80% train (188,636) | 20% test (47,159) Stratified to preserve class ratio Model: RandomForestClassifier n_estimators = 300 max_depth = None (fully grown) min_samples_leaf = 2 class_weight = "balanced" random_state = 42 Validation: StratifiedKFold(n_splits=5) CV F1 mean = 0.9948 ± 0.0004 ``` ### 模型表现 | 指标 | 数值 | |---|---| | 准确率 | **99.40%** | | 精确率 (malicious) | **99.41%** | | 召回率 (malicious) | **99.18%** | | F1 分数 (malicious) | **99.30%** | | ROC-AUC | **99.80%** | | CV F1 平均值 (5 折) | **99.48% ± 0.04%** | **混淆矩阵** (测试集,47,159 个样本): ``` Predicted Malicious Predicted Safe Actual Malicious 20,024 165 Actual Safe 118 26,852 ``` ### 特征重要性 (前 10 名) | 排名 | 特征 | 重要性 | 信号类型 | |---|---|---|---| | 1 | `IsHTTPS` | 39.12% | 二进制协议标志 | | 2 | `DigitRatioInURL` | 8.96% | 字符比率 | | 3 | `NoOfDegitsInURL` | 8.62% | 字符计数 | | 4 | `URLLength` | 8.37% | 长度指标 | | 5 | `LetterRatioInURL` | 5.27% | 字符比率 | | 6 | `PathEntropy` | 4.77% | 香农熵 | | 7 | `NoOfLettersInURL` | 3.81% | 字符计数 | | 8 | `URLEntropy` | 3.63% | 香农熵 | | 9 | `NoOfSubDomain` | 3.37% | 域名结构 | | 10 | `DomainLength` | 3.24% | 长度指标 | ### 推理 (`app.py → _run_prediction`) ``` # 1. 从 URL 字符串中提取 28 个特征 url_features = extract_features(url) # 2. 构建 DataFrame 以对齐训练列顺序 row = {col: url_features.get(col, 0) for col in FEATURE_COLUMNS} features_df = pd.DataFrame([row], columns=FEATURE_COLUMNS) # 3. 获取恶意概率 class_list = list(MODEL.classes_) # [0, 1] malicious_idx = class_list.index(0) # 0 prob = float(MODEL.predict_proba(features_df)[0][malicious_idx]) # 4. 应用校准阈值 if prob >= 0.90: verdict = "Malicious", "Critical" elif prob >= 0.70: verdict = "Suspicious", "Medium" else: verdict = "Safe", "Low" ``` ## 特征工程 所有 28 个特征均由 `feature_extraction.py` 仅从 URL 字符串中计算得出 —— 无需 DNS 查询、无需 WHOIS 查询、无需调用外部 API。 ### 长度特征 | 特征键 | 描述 | 类型 | |---|---|---| | `URLLength` | 归一化 URL 中的总字符数 | 整数 | | `DomainLength` | 主机名中的字符数 | 整数 | | `TLDLength` | 顶级域名中的字符数 | 整数 | ### 结构特征 | 特征键 | 描述 | 风险信号 | |---|---|---| | `NoOfSubDomain` | 超出 `domain.tld` 的子域名级别计数 | 高数量会掩盖真实域名 | | `URLDepth` | 路径中 `/` 字符的计数 | — | | `NoOfDotsInURL` | 完整 URL 中的点字符总数 | — | | `NoOfHyphensInURL` | 连字符总数 | 过多的连字符会模仿合法域名 | ### 字符计数特征 | 特征键 | 描述 | |---|---| | `NoOfLettersInURL` | 字母字符总数 | | `NoOfDegitsInURL` | 数字字符总数 | | `NoOfEqualsInURL` | `=` (查询参数分隔符) 计数 | | `NoOfQMarkInURL` | `?` 计数 | | `NoOfAmpersandInURL` | `&` 计数 | | `NoOfOtherSpecialCharsInURL` | 排除 `:/.-_?&=#@%` 外的特殊字符 | | `NoOfAtInURL` | `@` 计数 (浏览器会忽略 `@` 之前的所有内容) | | `NoOfPercentInURL` | `%` 计数 (百分号编码) | ### 比率特征 | 特征键 | 描述 | 公式 | |---|---|---| | `DigitRatioInURL` | 数字占比 | `digits / total_chars` | | `LetterRatioInURL` | 字母占比 | `letters / total_chars` | ### 熵特征 香农熵 `H = -Σ p(c) × log₂(p(c))` 用于衡量随机性。URL 或路径中的高熵表明存在混淆或随机生成的字符串。 | 特征键 | 计算对象 | |---|---| | `URLEntropy` | 完整的归一化 URL | | `DomainEntropy` | 仅主机名 | | `PathEntropy` | URL 路径部分 | ### 二进制 / 启发式特征 | 特征键 | 值为 1 代表 | 何时存在风险 | |---|---|---| | `IsHTTPS` | 存在 HTTPS 协议 | 0 = 无加密 (权重最高的特征) | | `IsIPAddress` | 主机为原始 IPv4 地址 | 总是可疑的 | | `HasPunycode` | 主机名中包含 `xn--` 标签 | 同形异义词攻击指标 | | `HasAtSign` | URL 中包含 `@` | 浏览器会忽略 `@` 之前的内容 | | `HasDoubleSlashRedirect` | `//` 出现多次 | 开放重定向模式 | | `HasHexEncoding` | `%` 字符超过 3 个 | 路径混淆 | | `IsSuspiciousTLD` | TLD 在已知危险集合中 | 参见下方的 TLD 列表 | | `BrandInSubdomain` | 品牌关键词在 URL 中,但不在注册域名中 | 欺骗尝试 | | `SuspiciousKeywordCount` | 匹配到的钓鱼关键词计数 | login、verify、password 等 | **检测的可疑 TLD:** `.xyz .tk .ml .ga .cf .gq .pw .top .click .link .work .party .download .zip .review .country .kim .science .cricket .win .webcam .faith .loan .diet .men .date` **检测的钓鱼关键词:** `login verify update secure account banking confirm password signin paypal webscr ebay amazon billing support service alert validation authentication authorize credential wallet recovery suspended locked urgent limited bonus prize` **品牌欺骗关键词:** `paypal apple google microsoft amazon netflix facebook instagram twitter linkedin dropbox chase wellsfargo bankofamerica citibank hsbc dhl fedex ups usps` ### URL 归一化 训练数据中的安全 URL 被统一格式化为 `https://www.domain.com`。为了在推理时匹配此,提取器通过在纯 HTTPS 根域名前添加 `www.` 来对其进行归一化: ``` # 归一化前:https://github.com → NoOfSubDomain = 0 # 归一化后:https://www.github.com → NoOfSubDomain = 1 # 这与模型训练时的特征分布相匹配 if is_https and no_of_subdomain == 0 and not is_ip_address(domain): url = url.replace(f"https://{domain}", f"https://www.{domain}", 1) ``` ## API 文档 ### `POST /api/v1/predict` 分析 URL 并返回带有风险评分、说明和提取特征的威胁判定结果。 **速率限制:** 每个 IP 每分钟 60 次请求 **请求:** ``` { "url": "https://example.com/path?query=value" } ``` **响应 (200 OK):** ``` { "prediction": "Safe", "risk_score": 0.0821, "risk_level": "Low", "confidence": 8.21, "explanations": [ "URL uses HTTPS — encrypted connection.", "URL length is within normal range.", "Domain structure appears straightforward.", "ML model assigns low risk — no strong phishing patterns detected in the URL." ], "reasons": [ "..." ], "features": { "URLLength": 22, "DomainLength": 14, "IsHTTPS": 1, "URLEntropy": 3.7544, "DomainEntropy": 3.3249, "PathEntropy": 0.0, "NoOfSubDomain": 1, "DigitRatioInURL": 0.0, "IsIPAddress": 0, "HasPunycode": 0, "IsSuspiciousTLD": 0, "SuspiciousKeywordCount": 0, "BrandInSubdomain": 0 } } ``` **预测值:** `"Safe"` | `"Suspicious"` | `"Malicious"` **风险级别值:** `"Low"` | `"Medium"` | `"Critical"` **阈值逻辑:** | `risk_score` | `prediction` | `risk_level` | |---|---|---| | ≥ 0.90 | Malicious | Critical | | ≥ 0.70 | Suspicious | Medium | | < 0.70 | Safe | Low | **错误响应:** | 状态码 | 响应体 | 原因 | |---|---|---| | `400` | `{"error": "Missing 'url' in request body."}` | `url` 字段为空或缺失 | | `400` | `{"error": "URL exceeds maximum length of 2048 characters."}` | URL 过长 | | `429` | `{"error": "Rate limit exceeded. Please slow down."}` | 超过速率限制 | | `503` | `{"error": "Model not loaded. Check server logs."}` | 模型在启动时加载失败 | | `500` | `{"error": "Prediction failed. Please try again."}` | 未处理的预测错误 | ### `GET /health` 返回服务健康状态。 ``` { "status": "ok", "model_loaded": true, "version": "v1" } ``` 如果模型加载失败,将返回带有 `"status": "degraded"` 的 `503` 状态码。 ### `GET /api/v1/metrics` 返回上次运行 `train_model.py` 时保存的模型训练指标。 ``` { "accuracy": 0.994, "precision": 0.9941, "recall": 0.9918, "f1_score": 0.993, "roc_auc": 0.998, "cv_f1_mean": 0.9948, "cv_f1_std": 0.0004, "confusion_matrix": [[20024, 165], [118, 26852]], "feature_count": 28, "feature_columns": ["URLLength", "..."], "feature_importance": [{"feature": "IsHTTPS", "importance": 0.391238}, "..."], "train_samples": 188636, "test_samples": 47159, "n_estimators": 300 } ``` ### `GET /` 服务发现端点。 ``` { "service": "URLShield API", "version": "v1", "status": "running", "model_loaded": true } ``` ## 项目结构 ``` threatlens/ ├── backend/ │ ├── app.py # Flask application, routes, prediction logic │ ├── feature_extraction.py # 28-signal URL feature engineering │ ├── train_model.py # Training pipeline, CV, metrics export │ ├── config.py # Environment-driven configuration │ ├── url_model.pkl # Serialized (model, feature_columns) tuple │ ├── model_metrics.json # Saved evaluation metrics from last training run │ ├── test_api.py # API integration tests │ └── test_data.py # Feature extraction unit tests │ ├── frontend/ │ ├── index.html # Single-page application │ ├── script.js # Fetch API, DOM, localStorage, progress animation │ └── style.css # Design system, CSS custom properties, responsive │ ├── dataset/ │ └── phishing_urls.csv # 235,795 labeled URLs (0=malicious, 1=safe) │ ├── Dockerfile # Multi-stage build, non-root user, Gunicorn ├── docker-compose.yml # Backend + Nginx frontend services ├── nginx.conf # Static serving + /api/ reverse proxy ├── requirements.txt # Pinned Python dependencies ├── .env.example # Environment variable template └── README.md ``` ## 技术栈 ### 前端 | 技术 | 版本 | 用途 | |---|---|---| | HTML5 | — | 语义化标记、ARIA 无障碍支持 | | 原生 JavaScript | ES2022 | Fetch API、DOM 操作、localStorage | | CSS3 | — | 自定义属性、Grid、Flexbox、动画 | | Google Fonts | — | Inter (UI) + JetBrains Mono (URL/代码) | ### 后端 | 技术 | 版本 | 用途 | |---|---|---| | Python | 3.12 | 运行时 | | Flask | 3.1.0 | HTTP API 框架 | | flask-cors | 6.0.0 | 跨域资源共享 | | flask-limiter | 4.1.1 | 基于 IP 的速率限制 | | pandas | 2.2.3 | 特征 DataFrame 构建 | | python-dotenv | 1.1.0 | `.env` 文件加载 | | gunicorn | 23.0.0 | 生产级 WSGI 服务器 | ### 机器学习 | 技术 | 版本 | 用途 | |---|---|---| | scikit-learn | 1.9.0 | `RandomForestClassifier`、指标、交叉验证 | | numpy | 1.26.4 | 数组操作 | | joblib | 1.4.2 | 模型序列化/反序列化 | ### 部署 | 技术 | 用途 | |---|---| | Docker | 多阶段镜像构建 | | Docker Compose | 服务编排 | | Nginx Alpine | 静态文件服务 + API 反向代理 | | Render | 托管后端 (`urlsheild-api.onrender.com`) | ## 安全考量 ### 输入验证 (`app.py`) - URL 长度上限为 **2,048 个字符** —— 防止内存耗尽 - 空的 URL 输入会在到达 ML pipeline 之前被以 `400` 拒绝 - 接受无协议的 URL (特征提取器会自动添加 `http://`),在不放宽验证规则的前提下提升易用性 - 所有面向用户的错误消息均经过净化处理 —— 堆栈跟踪仅记录在服务端,绝不返回给客户端 ### 前端输出编码 (`script.js`) 所有用户提供的内容和 API 响应字符串在插入 DOM 之前,都会经过 `escapeHtml()` 处理: ``` function escapeHtml(str) { return String(str) .replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">") .replaceAll('"', """).replaceAll("'", "'"); } ``` 这可以防止恶意 URL 字符串或被篡改的 API 响应引发存储型/反射型 XSS。 ### HTTP 安全响应头 (`nginx.conf`) ``` add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; ``` ### 运行时隔离 (`Dockerfile`) 容器以非 root 用户 (`appuser`) 身份运行,并挂载只读的模型卷,从而最大程度地缩小任何运行时安全漏洞的影响范围。 ### 速率限制 - **全局:** 每个 IP 每天 200 次请求 - **预测端点:** 每个 IP 每分钟 60 次请求 - 通过具有内存存储的 `flask-limiter` 实现;违规时返回 `429` ## 性能 - **特征提取是纯 Python 实现** —— 没有子进程调用、没有 I/O、没有网络请求。对于 200 个字符的 URL,典型提取时间在 5ms 以内。 - **模型在启动时加载一次** (通过 `joblib.load()`) 并驻留内存。无需按请求进行磁盘 I/O。 - **具有 300 棵树的 RandomForest** 在单个 CPU 核心上对单个特征向量实现低于 10ms 的推理速度。 - **Gunicorn 在 Docker 部署中运行 2 个 worker**,为托管环境提供基础的并发处理能力。 - **Nginx 直接提供前端静态资源服务** —— 静态文件永远不会经过 Flask。 - **由 localStorage 支持的扫描历史** 意味着历史记录面板完全不需要 API 调用。 ## 挑战与工程经验 ### 1. 训练/推理特征对齐 **挑战:** 早期版本的 pipeline 基于数据集提供的 54 个特征进行训练,但推理路径在运行时只能计算出 11 个仅限 URL 的特征 —— 这种隐蔽的不匹配导致产生了完全错误的预测。 **解决方案:** 重写了 `train_model.py`,在每个训练 URL 上调用相同的 `extract_features()` 函数,从而保证训练和推理之间的特征工程代码路径完全一致。模型工件是一个 `(model, feature_columns)` 元组 —— 列表会随模型一起保存,以防止未来出现任何特征漂移。 ### 2. ML 审计与误报发现 **挑战:** 部署后,诸如 `https://github.com/user/repo` 之类的合法 URL 被以 99.8% 的置信度归类为 Malicious —— 这显然是错误的。 **调试过程:** 1. 在独立的审计脚本中端到端追踪完整的预测流程 2. 检查标签映射 (`model.classes_ = [0, 1]`, 索引 0 = malicious) —— 正确 3. 检查概率提取 (`proba[malicious_idx]`) —— 正确 4. 将已知安全的 URL 输入模型 —— 像 `github.com` 这样的根域名得分约为 45% 恶意 5. 对 `https://github.com` 和 `https://github.com/user/repo` 之间进行了特征差异分析 **找到根本原因:** 两个复合问题: - **训练数据分布偏差** —— 数据集中的安全 URL 几乎全是 `https://www.domain.com` 根域名。模型在训练期间从未见过具有长且混合大小写路径的安全 URL。在训练集中,较高的 `PathEntropy` (典型 GitHub 路径为 4.03 比特) 与恶意 URL 存在极强的正相关性。 - **阈值误校准** —— 对于具有这种训练分布的模型来说,最初 0.85 的 Malicious 阈值过于激进。 **已实施修复:** 在 `app.py` 中将阈值从 `(0.85, 0.55)` 提高到 `(0.90, 0.70)`。作为长期修复方案,需要使用分布更均衡的安全 URL 重新训练,以解决底层数据集的偏差。 ### 3. URL 归一化策略 **挑战:** 不带 `www.` 的根域名 (`github.com`) 在安全训练类别中占比不足,导致即使在调整阈值后仍处于边缘分数。 **解决方案:** `feature_extraction.py` 在计算特征之前,通过添加 `www.` 前缀来归一化纯 HTTPS 根域名,使推理输入与训练安全 URL 中的主要分布模式保持一致。这将大多数主要域名的根域名得分从约 45% 降至安全区间。 ### 4. 生产环境工件中的编码损坏 **挑战:** 通过 PowerShell 的 `Set-Content` 写入的文件在 `script.js` 中引入了 Windows-1252 乱码 —— 像 `—`、`…`、`≈` 这样的 Unicode 字符被存储为多字节的 Latin-1 序列 (`â€"`、`…`)。这些在某些编辑器中是不可见的,但在浏览器中会错误渲染。 **解决方案:** 通过字节级检查识别出所有损坏的序列,使用带有显式 `UTF8` 编码的 `System.IO.File.WriteAllText` 执行了完整文件重写,并将代码/注释中所有的 Unicode 排版字符替换为等效的纯 ASCII 字符。 ## 已知局限性 ### 数据集分布偏差 训练数据集的安全类别几乎完全由 `https://www.domain.com` 根域名 URL 组成。这造成了系统性偏差:模型对具有长路径、查询参数或混合大小写路径段的合法 URL 接触有限。诸如 `PathEntropy`、`URLLength` 和 `LetterRatioInURL` 等特征在安全类别中的分布并不能反映现实世界中安全 URL 的多样性。 **观察到的影响:** 包含有意义路径的 URL (例如 GitHub 仓库 URL、维基百科文章链接) 的得分可能会高于预期,即使其他所有信号都是正常的。 ### PathEntropy 敏感度 `PathEntropy` 是第 6 重要的特征 (重要性 4.77%)。像 `/user/repository-name` 这样合法的长路径所产生的香农熵,与混淆过的钓鱼路径相当。由于训练集中缺乏足够多包含长路径的安全 URL 示例,模型无法区分这两者。 ### 阈值与重新训练的权衡 阈值调整 (`0.90` / `0.70`) 减少了边缘情况下的误报,但这只是校准补丁,并非结构性修复。如果 URL 的路径熵足够高,导致预测置信度超过 0.90,无论阈值如何,它仍然会被错误地标记。正确的长期修复方案是,在包含根域名、路径和查询参数且具有均衡安全 URL 表示的数据集上进行重新训练。 ## 未来改进 - **在增强的数据集上重新训练** —— 添加来自 Common Crawl、维基百科、GitHub API 和 Stack Overflow 等来源的真实路径的安全 URL - **移除或限制 PathEntropy** —— 审计表明其泛化能力很差;将其从 `MODEL_FEATURE_COLUMNS` 中排除并重新训练,将减少合法的长路径 URL 的误报 - **威胁情报集成** —— 可选择性地利用实时的 WHOIS 域名年龄、DNS 信誉和证书透明度日志数据来丰富预测结果 - **置信区间** —— 暴露 Random Forest 中树级别的方差,让用户感知预测的不确定性 - **批量扫描 API** —— `POST /api/v1/predict/batch` 接受 URL 数组,以应对集成用例 - **Webhook 支持** —— 将扫描结果推送到回调 URL,以便进行自动化 pipeline 集成 - **持久化扫描历史** —— 用后端持久化的扫描日志替换 `localStorage`,以实现跨设备访问 - **模型版本控制** —— 对 `url_model.pkl` 工件进行版本控制,并通过 `/health` 暴露当前活动版本 - **可解释性改进** —— 集成 SHAP 值,在当前基于规则的说明之外,提供细粒度的特征贡献得分 ## 安装 ### 前置条件 - Python 3.12+ - pip - Docker + Docker Compose (用于容器化部署) ### 本地开发 ``` # 克隆仓库 git clone https://github.com/your-username/threatlens.git cd threatlens # 创建并激活虚拟环境 python -m venv venv source venv/bin/activate # Linux/macOS venv\Scripts\activate # Windows # 安装依赖 pip install -r requirements.txt # 配置环境 cp .env.example .env # 编辑 .env — 设置 SECRET_KEY 和任何覆盖项 # 启动后端(从 backend/ 目录) cd backend python app.py # API 可在 http://localhost:5000 访问 # 提供前端服务(在单独的终端中,从项目根目录) # 任何静态文件服务器均可 — 例如 Python 内置的: python -m http.server 3000 --directory frontend # 前端可在 http://localhost:3000 访问 ``` ### Docker 部署 ``` # 构建并启动所有服务 docker-compose up --build # 后端 API:http://localhost:8000 # 前端:http://localhost:3000 # 健康检查:http://localhost:8000/health # 在 detached 模式下运行 docker-compose up -d --build # 查看日志 docker-compose logs -f backend # 停止所有服务 docker-compose down ``` ### 重新训练模型 ``` # 从 backend/ 目录 cd backend # 确保数据集存在于 ../dataset/phishing_urls.csv python train_model.py # 输出: # url_model.pkl — 新模型产物 # model_metrics.json — 更新后的指标 ``` ## 配置 所有配置均通过环境变量管理。运行前请将 `.env.example` 复制为 `.env`。 | 变量 | 默认值 | 描述 | |---|---|---| | `FLASK_DEBUG` | `false` | 启用 Flask 调试模式 | | `SECRET_KEY` | `change-me-in-production` | Flask 会话密钥 —— **生产环境中必须修改** | | `MODEL_PATH` | `url_model.pkl` | 模型工件路径 (相对于 `backend/`) | | `DATASET_PATH` | `../dataset/phishing_urls.csv` | 训练数据集路径 | | `METRICS_PATH` | `model_metrics.json | 指标 JSON 输出路径 | | `ALLOWED_ORIGINS` | `*` | CORS 允许的源 —— 在生产环境中应进行限制 | | `RATE_LIMIT_DEFAULT` | `200 per day` | 每个 IP 的全局速率限制 | | `RATE_LIMIT_PREDICT` | `60 per minute` | 预测端点的速率限制 | | `LOG_LEVEL` | `INFO` | Python 日志记录级别 | | `PORT` | `5000` | Flask/Gunicorn 绑定端口 | ### 本项目展示的技能 | 技能领域 | 证明 | |---|---| | **全栈开发** | 完整的端到端系统:原生 JS 单页应用前端、Flask REST API 后端、Nginx 反向代理、Docker Compose 编排 | | **机器学习集成** | 在 23.5 万个样本上训练的 RandomForest、28 项特征工程 pipeline、`predict_proba()` 概率提取、校准阈值 | | **API 设计** | 带有版本控制 (`/api/v1/`) 的 RESTful 端点、结构化的错误响应、速率限制、健康检查、向后兼容的 `/predict` 别名 | | **特征工程** | 香农熵、字符比率分析、基于正则的启发式规则、域名归一化 —— 全部零依赖、零网络 I/O | | **网络安全知识** | 钓鱼信号分析、Punycode 同形异义词检测、品牌欺骗检测、可疑 TLD 分类、通过输出编码预防 XSS | | **系统设计** | 通过共享代码路径对齐训练/推理特征、带有列名列表的模型工件版本控制、环境驱动的配置、非 root 的 Docker 运行时 | | **调试与模型审计** | 端到端预测追踪、标签映射验证、概率反转检查、特征差异分析、根本原因识别 (数据集分布偏差 + PathEntropy 敏感度) | | **DevOps** | 多阶段 Dockerfile、Docker Compose 健康检查、Gunicorn 生产级 WSGI、Nginx 安全响应头、卷挂载的模型工件 | | **代码质量** | 按照关注点分离原则划分为 7 个专注的 Python 模块、一致的错误处理、结构化日志、锁定的依赖版本 | | **无障碍设计** | ARIA 实时区域、语义化 HTML、键盘导航、`role="search"`、焦点管理 | ## 许可证 MIT 许可证 —— 详情请参阅 [LICENSE](LICENSE)。 ## 致谢 - 数据集来源于公开可用的钓鱼 URL 研究数据集 - 感谢 [scikit-learn](https://scikit-learn.org/) 提供的 RandomForestClassifier 实现 - 感谢 [Flask](https://flask.palletsprojects.com/) 及其扩展生态系统 - 感谢 Rasmus Andersson 提供的 [Inter](https://rsms.me/inter/) 字体 - 感谢 [JetBrains Mono](https://www.jetbrains.com/lp/mono/) 用于等宽 URL 渲染
标签:Apex, URL检测, Web安全, 威胁情报, 开发者工具, 机器学习, 版权保护, 蓝队分析, 请求拦截, 逆向工具, 钓鱼检测