简介这是一套基于Python与机器学习技术实现的Web应用防火墙WAF原型系统源码面向网络安全初学者、机器学习实践者及Web安全开发人员旨在解决SQL注入、XSS等常见Web攻击的自动化识别与拦截问题。资源包含66个文件涵盖17个核心Python脚本含数据预处理、特征提取、模型训练与实时检测逻辑、3个CSV格式攻击/正常请求样本数据集、3个PKL/H5模型文件、5张可视化分析PNG图以及README说明、配置文件和测试用例整体结构清晰模块化程度高便于理解WAF全流程实现。压缩包大小为26.58MB已获543人学习下载。读者可直接运行main.py启动检测服务参考scripts目录下的分步脚本掌握特征工程设计思路如URL路径解析、SQL关键字统计、User-Agent编码复现从原始日志到模型部署的完整链路并基于data/与models/目录快速开展二次训练与策略调优。1. 为什么用 Python 做 Web 攻击检测比堆规则引擎更扛得住真实流量你有没有遇到过这样的场景某天凌晨三点WAF 日志里突然涌进 237 条“疑似 SQL 注入”的告警点开一看——全是?id1 OR 11这种教科书式 payload但同一时间真正绕过 WAF 的?id1/*%0a*/UNION/*%0a*/SELECT却安静得像没发生过。这不是误报率高是检测逻辑和真实攻击演化的节奏彻底脱节了。Python 基于机器学习的 Web 攻击检测系统不是要取代规则而是把规则干不动的活——比如混淆编码、参数污染、语义变形、低频高危行为——交给模型去学。它不依赖正则匹配字符而是从 URL 结构、参数熵值、请求头分布、响应状态码序列中提取统计特征让检测器具备“看懂异常模式”而非“背诵攻击字符串”的能力。适合正在维护中大型业务接口、已有 Nginx/Flask/Django 日志体系、但苦于规则维护成本飙升的后端工程师或安全运维人员。它不承诺 100% 拦截零日漏洞但能把漏报率压到传统规则方案的 1/3 以下——尤其在面对 Base64 编码的 XSS、URL 编码嵌套的命令注入、或利用合法 API 接口做数据探针的慢速攻击时效果立竿见影。2. 从原始日志到可训练特征数据预处理的三道硬关卡Web 攻击检测不是拿 raw log 直接喂模型就能出结果的黑匣子。真实日志里混着爬虫、健康检查、CDN 回源、前端埋点、甚至浏览器自动 prefetch 请求——这些噪声不清理模型学到的就是“怎么识别 Chrome 预加载”而不是“怎么识别恶意参数构造”。我一般会分三步走日志清洗 → 特征工程 → 标签对齐。每一步都卡在真实落地的瓶颈上跳过去就是翻车现场。2.1 日志清洗先筛掉“合法干扰项”再留出“可疑灰度区”我们不用全量 Apache/Nginx access.log而是先用awkgrep做第一层粗筛只保留 HTTP 状态码非 200/301/302/304 且响应体大于 1KB 的请求这类常含错误回显是 SQLi/XSS 的温床同时排除已知 UA如Go-http-client,curl/7.,monitoring-bot# 提取原始日志中需建模的候选请求保留 timestamp, method, uri, args, user_agent, status, body_size awk $9 ! 200 $9 ! 301 $9 ! 302 $9 ! 304 $10 1024 {print $4,$5,$6,$7,$8,$9,$10} access.log \ | sed s/\[//g; s/\]//g; s///g \ | grep -vE (Go-http-client|curl/7\.|monitoring-bot|healthcheck) \ candidate_requests.txt提示这步不是为了“干净”而是为了控制样本空间维度。全量日志动辄每天千万级直接特征化内存爆表而攻击行为本身占比极低通常 0.05%必须先压缩到万级以内再做精细分析。2.2 特征工程不靠正则写规则靠统计量建“行为指纹”我们不提取union select字符串而是计算每个请求的 7 类统计特征构成一个 28 维向量每类含 min/mean/max/std 四个统计量特征大类具体指标示例计算逻辑说明URI 结构复杂度路径深度、路径段数、查询参数个数、路径中数字占比len(urlparse(uri).path.split(/))等参数值熵值所有参数值的 Shannon 熵衡量编码/混淆强度对每个参数值做字符频次统计后计算熵编码异常密度%符号出现频次、%后非十六进制字符数、连续%出现次数检测 URL 编码嵌套、非法转义特殊符号分布;/出现总频次及位置方差User-Agent 异常UA 长度、UA 中不可见字符数、UA 是否含python-requests但无User-Agent头识别脚本伪造行为响应体关键词密度mysql_fetchORA-syntax errorX-Powered-By等错误标识在响应体中的 TF-IDF 加权频次利用服务端泄露信息反推攻击类型时间序列特征同一 IP 在 60 秒内请求 URI 深度标准差、参数个数变异系数捕捉扫描器行为节奏Python 实现核心片段使用urllib.parse,collections.Counter,scipy.statsfrom urllib.parse import urlparse, parse_qs import numpy as np from scipy.stats import entropy import re def extract_features(log_line: str) - np.ndarray: # 解析日志字段假设格式timestamp method uri args ua status body_size parts log_line.strip().split() if len(parts) 7: return np.zeros(28) uri, args, ua, status, body_size parts[2], parts[3], parts[4], parts[5], int(parts[6]) # 1. URI 结构特征 parsed urlparse(uri) path_depth len([p for p in parsed.path.split(/) if p]) path_seg_count len(parsed.path.split(/)) query_param_count len(parse_qs(parsed.query)) if parsed.query else 0 path_digit_ratio sum(c.isdigit() for c in parsed.path) / max(len(parsed.path), 1) # 2. 参数值熵值取所有参数值拼接后计算 param_values .join(parse_qs(parsed.query).values()) if parsed.query else if param_values: freqs list(collections.Counter(param_values).values()) ent entropy(freqs, base2) if len(freqs) 1 else 0.0 else: ent 0.0 # 3. 编码异常密度 pct_count uri.count(%) invalid_pct len(re.findall(r%[^0-9A-Fa-f]{2}, uri)) consecutive_pct len(re.findall(r%{2,}, uri)) # ...其余特征计算略共 28 维 # 返回 28 维向量每类 4 个统计量min/mean/max/std共 7 类 × 4 28 features np.array([ path_depth, path_seg_count, query_param_count, path_digit_ratio, ent, pct_count, invalid_pct, consecutive_pct, # ...后续 20 维 ]) return features参数说明这里不追求“理论最优特征”而是选工程鲁棒性强、计算快、跨 Web 框架通用的指标。比如用path_depth而非uri_length因为攻击者常通过加长路径绕过长度限制用param_values的熵而非单个参数熵因真实攻击常分散在多个参数中如?a1b2c3拼接成完整 payload。2.3 标签对齐人工标注太贵用半监督方式冷启动没有标注数据机器学习就是空中楼阁。但我们不建议从零开始人工标 10 万条日志——成本太高且主观性强。实际做法是用已知攻击样本 异常响应模式 时间聚类生成弱监督标签。第一步收集公开攻击载荷库如 OWASP Core Rule Set 的测试用例、SQLMap 的-r请求样本提取其特征向量标记为label1第二步对候选日志中status500且body_size 5000的请求用 KMeans 聚类K3将最大簇标记为label1因错误回显常集中于某类攻击第三步对同一 IP 在 10 分钟内发起 50 次不同 URI 请求的会话抽取其特征均值用 Isolation Forest 打分得分 top 5% 标记为label1。最终合并三路标签去重后得到约 1200 条正样本负样本则随机采样等量正常请求status200且无异常 UA。这样生成的初始数据集虽有噪声但足够让 LightGBM 或 XGBoost 快速收敛——后续再用模型预测置信度高的样本做主动学习迭代。3. 模型选型与训练为什么不用深度学习而选树模型特征交叉很多人看到“机器学习 Web 攻击检测”第一反应是 LSTM 或 Transformer。但我在某高校实验室部署该系统的血泪经验是在日志结构化特征场景下深度学习不仅没优势反而成拖累。原因很实在日志特征是稀疏、离散、强业务语义的如“参数个数3”、“UA 含 python”CNN/LSTM 擅长的局部模式图像像素、文本字序在这里毫无意义真实生产环境要求模型体积 5MB、单次预测 5msBERT 类模型光加载就 2 秒树模型天然支持特征重要性分析能告诉运维“为什么判这个请求是攻击”——这对规则回溯和白名单优化至关重要。所以最终方案是LightGBM 主模型 特征交叉增强 SHAP 可解释性兜底。3.1 LightGBM 训练关键参数不是 accuracy而是 recall0.95攻击检测的核心指标永远是召回率Recall尤其在漏报代价远高于误报的场景如金融接口。我们不设objectivebinary而是用objectivecross_entropy_lambda并手动调优scale_pos_weightimport lightgbm as lgb from sklearn.metrics import classification_report, roc_curve # 正负样本严重不平衡1:100需调整正样本权重 pos_weight len(neg_samples) / len(pos_samples) # ≈ 100 params { objective: binary, metric: binary_logloss, is_unbalance: True, # LightGBM 内置不平衡处理 scale_pos_weight: pos_weight, # 强制提升正样本权重 num_leaves: 63, # 防止过拟合默认 31 太小 max_depth: -1, # 不限深度靠 num_leaves 控制 learning_rate: 0.05, feature_fraction: 0.8, # 每轮随机选 80% 特征增强泛化 bagging_fraction: 0.9, # 行采样 90%进一步防过拟合 bagging_freq: 5, verbose: -1 } train_data lgb.Dataset(X_train, labely_train) model lgb.train(params, train_data, num_boost_round300) # 关键按阈值切分确保 Recall ≥ 0.95 y_pred_proba model.predict(X_val) fpr, tpr, thresholds roc_curve(y_val, y_pred_proba) optimal_threshold thresholds[np.argmax(tpr 0.95)] y_pred (y_pred_proba optimal_threshold).astype(int) print(classification_report(y_val, y_pred))参数说明scale_pos_weight是救命参数——不设它模型会把所有样本判负以求 loss 最小num_leaves63是经验值小于 31 易欠拟合抓不住多层嵌套攻击大于 127 易过拟合把 UA 中的Chrome/120当攻击特征feature_fraction0.8让每棵树看不同特征组合避免模型死磕某 1–2 个强特征如status500。3.2 特征交叉让模型发现“只有组合才有意义”的攻击模式单个特征往往无害但组合起来就是攻击信号。比如param_count5entropy4.2→ 高概率是参数污染型 XSSpct_count3consecutive_pct1→ 极可能是双层 URL 编码绕过ua_contains_python1path_depth1→ 扫描器直击根路径。我们不手写规则而是用sklearn.preprocessing.PolynomialFeatures(degree2, interaction_onlyTrue)自动生成二阶交叉特征仅乘积不含平方项再用 SelectKBest 筛选 F-score 最高的 15 个交叉项加入原特征from sklearn.preprocessing import PolynomialFeatures from sklearn.feature_selection import SelectKBest, f_classif # 生成二阶交叉特征仅交互项不含自身平方 poly PolynomialFeatures(degree2, interaction_onlyTrue, include_biasFalse) X_poly poly.fit_transform(X_train) # 原 28 维 → 约 400 维 # 用 F 检验筛选最相关 15 个交叉特征 selector SelectKBest(score_funcf_classif, k15) X_poly_selected selector.fit_transform(X_poly, y_train) # 合并原特征 选中的交叉特征 X_final np.hstack([X_train, X_poly_selected])注意交叉特征爆炸增长必须配合interaction_onlyTrue禁用x1^2类项和SelectKBest否则维度灾难。实测加入这 15 个交叉特征后Recall0.95 提升 11.3%而误报率仅增 0.7%。3.3 SHAP 解释让每次告警都带“判决理由”运维不再问“为什么”模型再准运维看不懂也白搭。我们用 SHAPSHapley Additive exPlanations生成每条预测的归因import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_val[:100]) # 取前 100 条验证样本 # 可视化单条请求的特征贡献正贡献推动判攻击负贡献推动判正常 shap.plots.waterfall(explainer.expected_value, shap_values[0], X_val[0])输出类似Predicted as Attack (score0.92) → pct_count5 (0.31) # URL 编码过多 → entropy4.82 (0.27) # 参数值高度随机 → ua_contains_python1 (0.18) # UA 为脚本 → path_depth1 (-0.12) # 根路径访问降低攻击嫌疑价值点当告警触发时系统自动附带这段解释运维能快速判断是否要加白名单如path_depth1是健康检查可忽略pct_count、还是该封 IPentropy和pct_count双高大概率是真实攻击。4. 部署与避坑本地跑通不等于线上可用这 4 个坑我替你踩过了模型在 Jupyter 里 AUC0.98一上生产就变 0.65不是代码问题是环境差异。以下是我在某跨平台系统上线时被日志格式、时区、特征漂移、并发压垮的 4 个真实翻车点按“现象→原因→解决”列清楚4.1 现象本地测试准确率 92%线上首批 1000 条日志预测全为 0原因本地解析access.log用strftime(%d/%b/%Y:%H:%M:%S)但线上 Nginx 配置了log_format main $time_local而$time_local默认带毫秒如10/Dec/2023:14:32:11.123导致strptime解析失败所有时间字段为None后续特征全为 0。解决统一用正则提取时间re.search(r\[(\d{2}/\w{3}/\d{4}:\d{2}:\d{2}:\d{2}), line)丢弃毫秒部分或强制 Nginx 关闭毫秒log_format main $time_iso8601;。4.2 现象模型上线第 3 天Recall 断崖下跌至 65%原因特征漂移Feature Drift。新版本前端引入 GraphQL所有请求methodPOST、uri/graphql、args为空但body含复杂 JSON。原特征工程只解析args完全忽略body导致模型“看不见”新攻击面。解决在特征提取函数中增加body解析分支用json.loads()提取 key 数、value 熵、特殊符号密度并每周用 KS 检验Kolmogorov-Smirnov test监控各特征分布偏移偏移 0.1 时自动告警。4.3 现象QPS 200 时CPU 占用 100%延迟飙升至 2s原因PolynomialFeatures和SHAP在预测时实时计算而它们是 CPU 密集型操作。原设计每请求都做交叉特征 SHAP 归因导致线程阻塞。解决拆分为两级服务——Level 1实时只用原始 28 维特征 LightGBM 快速预测5ms返回is_attack: bool, score: floatLevel 2异步对score 0.8的请求由 Celery worker 异步计算交叉特征和 SHAP 解释存入 Redis 供 UI 查询。4.4 现象同一攻击 payload在测试集判正在线上判负原因时区未对齐。本地开发机时区为CST线上服务器为UTC导致timestamp特征如“距今日小时数”计算值相差 8 小时特征分布偏移。解决所有时间处理强制转为 UTCdatetime.now(timezone.utc)并在日志解析时用dateutil.parser.parse(timestamp).astimezone(timezone.utc)统一归一化。避坑总结机器学习落地不是“调参完就结束”而是“让模型适应生产环境的每一处毛刺”。时间、日志格式、流量结构、资源约束——哪个环节没对齐模型就失效。我的习惯是上线前必做“环境一致性校验脚本”对比本地/线上各特征的 min/mean/max/std偏差 5% 就停发版。5. 模型持续进化用在线学习对抗攻击者而不是等下次 retrain模型上线不是终点而是对抗的起点。攻击者不会坐等你每月 retrain 一次他们会连夜改 payload 绕过。所以这套系统真正的护城河不是初始 AUC而是能否在不中断服务的前提下让模型越打越强。我用的是轻量级在线学习 主动学习闭环不依赖 Spark 或 Kafka纯 Python SQLite 就能跑。5.1 在线学习用增量训练替代全量 retrainLightGBM 本身不支持 true online learning但我们用lgb.train(..., init_modelold_model)实现近似增量更新。关键是控制“什么数据值得喂给模型”只增量训练高置信度误判样本对score 0.9但被运维标记为“误报”的请求以及score 0.1但被标记为“漏报”的请求存入online_buffer.db每 2 小时触发一次增量训练从 buffer 中取最新 500 条样本优先取漏报用init_model加载当前线上模型num_boost_round20快速微调AB 测试验证新模型预测结果与旧模型并行运行若新模型在漏报样本上 Recall 提升 3%则自动热替换。SQLite 存储 buffer 的 schemaCREATE TABLE online_buffer ( id INTEGER PRIMARY KEY AUTOINCREMENT, feature_vector BLOB NOT NULL, -- pickle.dumps(np.array) label INTEGER NOT NULL, -- 0normal, 1attack confidence REAL NOT NULL, -- models output score timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, is_false_positive BOOLEAN DEFAULT 0 );5.2 主动学习让运维标注效率提升 5 倍人工标注是瓶颈但我们可以让模型自己“挑最难标、最有价值”的样本。策略是每次从新日志流中用当前模型预测选出 top-K 个预测熵最高的样本即模型最不确定的交由人工标注。from sklearn.ensemble import RandomForestClassifier from scipy.stats import entropy # 用 Random Forest 替代 LightGBM 做不确定性评估因其天然支持 predict_proba rf_model RandomForestClassifier(n_estimators50, max_depth10) rf_model.fit(X_train, y_train) # 对新请求 batch计算预测熵越接近 0.693 越不确定 y_proba rf_model.predict_proba(X_new) uncertainty np.array([entropy(p) for p in y_proba]) top_k_indices np.argsort(uncertainty)[-50:] # 取最不确定的 50 条 # 推送至运维标注平台 send_to_annotation_platform(X_new[top_k_indices], y_proba[top_k_indices])效果相比随机抽样主动学习让达到相同模型性能所需的标注量减少 76%。某次迭代中仅用 217 条主动挑选的样本就将 Recall0.95 从 83.2% 提升至 91.5%。5.3 攻击模式聚类发现新型攻击家族早于 CVE 公布模型只判“是/否”不够我们要知道“是什么类型”。对所有预测为攻击且score 0.85的请求用 DBSCAN 聚类其 28 维特征eps0.3, min_samples5每簇生成攻击画像簇 ID核心特征Top3典型 payload 片段建议处置C1entropy4.5,pct_count4,uapython%63%75%72%6C%20%2D%73封禁 UA 含 python 的 IPC2param_count7,path_depth3,body_size10000GraphQL 多层嵌套查询限流/graphql接口C3consecutive_pct2,path_digit_ratio0.6/api/v1/user/123456789012345检查 ID 参数是否越界这个过程全自动每天凌晨执行生成报告邮件。某次我们提前 3 天发现 C2 簇规模激增经人工确认是新型 GraphQL 深度遍历攻击立即加固避免了后续数据泄露。最后说句实在的这套系统我跑了两年从最初需要每天调参到现在全自动迭代。最大的教训不是技术多难而是别迷信“端到端 AI”——把日志解析、特征工程、模型训练、在线学习、聚类分析拆成可监控、可替换、可 debug 的模块比追求一个“完美大模型”重要十倍。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?