
简介这是一套面向计算机专业学生与安全初学者的机器学习Web攻击检测系统完整源码包可直接用于毕业设计、期末大作业或课程设计。项目以WAF为核心融合机器学习模型对HTTP流量进行攻击识别包含数据预处理、特征提取、模型训练与检测展示等模块代码注释详尽新手也能读懂并快速部署运行。压缩包共69个文件约26.55MB以17个py源码文件为主辅以pkl、pickle、h5、pb等模型与权重文件csv、pcap、label等数据集以及txt、md说明文档和png、jpg界面截图结构清晰、便于按模块查阅。目前已有136人学习下载。资源内含详细文档说明与运行环境配置涵盖词向量模型、训练数据与检测界面读者可据此掌握从数据到模型落地的完整流程并在此基础上二次开发或撰写论文具备较高的实际应用与参考价值。1. 从一份能跑通的 Web 攻击检测源码说起它到底解决了什么很多做安全方向毕业设计或期末大作业的人卡点往往不在算法本身而在“数据怎么进、模型怎么出、检测结果怎么给人看”这条链路上。这份基于机器学习的 Web 攻击检测系统源码核心价值就是把这条链路完整串了起来从 HTTP 请求日志的解析、特征提取到模型训练、离线评估再到一个能输入 URL 或请求报文、实时返回“正常/攻击”判定的演示界面。它适合三类人一是需要快速搭出可演示系统的毕业生二是想理解安全检测工程化流程的初学者三是手里有日志数据、想套一个现成框架做实验的从业者。源码包里通常包含数据处理脚本、模型训练脚本、Flask 或 Django 写的 Web 接口以及一份说明文档把环境依赖、运行顺序和参数含义都列清楚了。你拿到手后最该先确认的不是模型准确率多高而是它默认支持哪些攻击类型、特征维度是多少、能不能替换成你自己的数据。2. 拆开源码看结构数据流、模型选型与目录职责2.1 典型目录布局与各文件职责拿到压缩包解压后先别急着pip install花五分钟把目录树看一遍能省掉后面很多“文件找不到”的麻烦。常见做法是分成data/、models/、src/、web/和docs/几个顶层目录。data/放原始日志和预处理后的特征文件models/存训练好的.pkl或.h5模型src/是核心代码web/是演示界面docs/里就是那份详细文档。下面是一个典型的目录结构不同版本可能略有差异但职责划分基本一致。web-attack-detection/ ├── data/ │ ├── raw/ # 原始 HTTP 日志如 access.log │ └── processed/ # 提取特征后的 CSV ├── models/ │ └── rf_model.pkl # 训练好的随机森林模型 ├── src/ │ ├── preprocess.py # 日志解析与特征提取 │ ├── train.py # 模型训练与评估 │ └── predict.py # 单条请求预测 ├── web/ │ ├── app.py # Flask 入口 │ └── templates/index.html ├── requirements.txt └── README.md这份结构里preprocess.py决定了系统能检测什么。它一般会从 URL、请求方法、参数里提取长度、特殊字符数量、是否包含 SQL 关键字、是否含脚本标签等统计特征。train.py负责读processed/下的 CSV划分训练测试集跑几个分类器做对比。predict.py是给 Web 层调用的输入一条请求文本输出标签和概率。web/app.py则把预测能力包装成 HTTP 接口前端页面负责收集输入并展示结果。理解了这个流向后面改代码就知道该动哪个文件。2.2 特征工程从原始请求到模型能吃的向量Web 攻击检测的难点不在分类器而在怎么把一条 HTTP 请求变成固定长度的数值向量。源码里常见的做法是提取以下几类特征请求行长度、URL 长度、参数个数、参数总长度、特殊字符如、、、、;、--出现次数、是否包含select、union、script、../等敏感词、数字与字母的比例、熵值等。这些特征不需要深度学习用pandas加正则就能算出来。下面这段代码模拟了从一条请求中提取特征的过程你可以对照源码里的preprocess.py看。import re import math from collections import Counter def entropy(s): 计算字符串的香农熵用来衡量随机性 if not s: return 0.0 counts Counter(s) length len(s) return -sum((c / length) * math.log2(c / length) for c in counts.values()) def extract_features(method, url, body): 从请求方法、URL 和 body 中提取数值特征 full f{method} {url} {body} features {} features[url_len] len(url) features[body_len] len(body) features[num_params] url.count() (1 if ? in url else 0) # 统计特殊字符出现次数 for ch in [, , , , ;, --, (, )]: features[fcount_{ch}] full.count(ch) # 敏感关键词命中 keywords [select, union, script, alert, ../, exec, drop] lower full.lower() for kw in keywords: features[fhas_{kw}] int(kw in lower) features[entropy] entropy(full) features[digit_ratio] sum(c.isdigit() for c in full) / (len(full) 1) return features if __name__ __main__: sample extract_features(GET, /product?id1 OR 11, ) for k, v in sample.items(): print(f{k}: {v})这段代码的逻辑很直白把请求拼成一个字符串然后统计长度、特殊字符、关键词和熵。参数说明上url_len和body_len反映请求规模异常长的 URL 往往是注入或溢出尝试count_*系列对 SQL 注入和 XSS 特别敏感has_*是布尔特征命中就置 1entropy越高说明字符串越杂乱可能是编码后的攻击载荷。你拿到源码后可以先用几条正常请求和几条带 OR 11的请求跑一遍看看特征值差异是否明显。如果差异不大说明特征设计有问题需要加特征或换提取方式。2.3 模型训练与评估为什么随机森林是常见起点源码里默认用的分类器多半是随机森林或 SVM少数会用朴素贝叶斯。随机森林在这个场景下受欢迎原因有几个对特征尺度不敏感不需要做归一化能输出特征重要性方便你解释哪些特征对判定攻击贡献大在小样本上也不容易过拟合。训练脚本一般会读processed/下的 CSV用train_test_split划分数据然后跑RandomForestClassifier最后打印准确率、召回率和混淆矩阵。下面是一个可复现的训练片段你可以直接替换成自己的数据路径。import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report, confusion_matrix # 读取预处理后的特征文件最后一列是标签 df pd.read_csv(data/processed/features.csv) X df.drop(columns[label]) y df[label] # 按 8:2 划分训练集和测试集random_state 固定保证可复现 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) clf RandomForestClassifier( n_estimators100, # 树的数量越多越稳但越慢 max_depthNone, # 不限制深度让树充分生长 min_samples_split2, # 节点分裂所需最小样本数 class_weightbalanced, # 攻击样本少时自动加权 random_state42 ) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred)) # 输出特征重要性方便判断哪些特征最有用 for name, imp in sorted(zip(X.columns, clf.feature_importances_), keylambda x: -x[1])[:10]: print(f{name}: {imp:.4f})参数上n_estimators设 100 是常见起点数据量上万后可以加到 200 到 300class_weightbalanced在攻击样本远少于正常样本时很关键不加的话模型会倾向于全判正常stratifyy保证划分后两类比例一致。跑完训练后重点看召回率而不是准确率因为漏报一个攻击比误报一个正常请求代价更高。如果召回率低于 0.9先别调模型回去检查特征里有没有漏掉关键攻击模式。3. 把系统跑起来环境配置、数据准备与接口调用3.1 环境依赖与版本对齐源码的requirements.txt里一般会列flask、scikit-learn、pandas、numpy、joblib这几个包。版本冲突是新手最容易翻车的地方尤其是scikit-learn和numpy的兼容性。常见做法是建一个虚拟环境然后按文档里给的版本装不要盲目pip install -U。下面是一套经过验证的安装流程Python 版本建议 3.8 到 3.10。# 创建虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 按文档锁定版本安装避免自动升级带来的接口变动 pip install flask2.2.5 pip install scikit-learn1.2.2 pip install pandas1.5.3 pip install numpy1.23.5 pip install joblib1.2.0 # 验证关键包版本 python -c import sklearn, flask, pandas; print(sklearn.__version__, flask.__version__, pandas.__version__)如果文档里没写具体版本就按上面这套来兼容性比较稳。装完后先跑python -c import sklearn确认没报错再继续下一步。遇到ImportError或DLL load failed九成是版本不匹配回退到文档指定版本即可。3.2 数据准备用自己的日志替换示例数据源码自带的示例数据通常只有几百条够跑通流程但不够训练出好模型。你要做的是把自己的访问日志整理成同样格式的 CSV。常见做法是先把日志解析成method、url、body、label四列label里 0 表示正常、1 表示攻击。如果你没有标注数据可以先用公开的 HTTP 攻击数据集或者用工具生成一批模拟攻击请求。下面这段脚本演示了如何把原始日志转成特征 CSV。import pandas as pd from preprocess import extract_features def build_dataset(log_path, label): 读取日志文件逐行提取特征并打标签 rows [] with open(log_path, r, encodingutf-8, errorsignore) as f: for line in f: parts line.strip().split( , 2) if len(parts) 3: continue method, url, body parts feats extract_features(method, url, body) feats[label] label rows.append(feats) return pd.DataFrame(rows) # 正常请求标 0攻击请求标 1 normal_df build_dataset(data/raw/normal.log, 0) attack_df build_dataset(data/raw/attack.log, 1) df pd.concat([normal_df, attack_df], ignore_indexTrue) df.to_csv(data/processed/features.csv, indexFalse) print(f总样本数: {len(df)}, 攻击样本: {df[label].sum()})这里的关键是extract_features必须和训练时用的是同一个函数否则特征列对不上模型会直接报错。label列名也要和训练脚本里读的一致。生成完 CSV 后先df.head()看一眼确认没有全空或全零的列再拿去训练。3.3 启动 Web 服务并调用预测接口模型训练好并保存成models/rf_model.pkl后就可以启动 Web 服务了。web/app.py里一般会加载模型然后暴露一个/predict接口接收 POST 请求返回 JSON 格式的判定结果。启动命令通常是python web/app.py默认跑在 5000 端口。下面用curl模拟一次调用你可以直接复制到终端里试。# 启动服务另开一个终端 python web/app.py # 发送一条正常请求 curl -X POST http://127.0.0.1:5000/predict \ -H Content-Type: application/json \ -d {method:GET,url:/index.html,body:} # 发送一条疑似 SQL 注入请求 curl -X POST http://127.0.0.1:5000/predict \ -H Content-Type: application/json \ -d {method:GET,url:/user?id1%27%20OR%20%271%27%271,body:}正常请求应该返回{label: 0, probability: 0.0x}注入请求返回{label: 1, probability: 0.9x}。如果两条都返回同一个标签先检查模型文件是否加载成功再检查特征提取函数是否和训练时一致。接口调通后打开浏览器访问http://127.0.0.1:5000就能看到演示页面输入 URL 或请求报文即可实时检测。4. 避坑与排查那些文档里不会写的翻车点4.1 特征列顺序不一致导致预测全错现象训练时准确率 0.95Web 接口调用却把所有请求都判成正常。原因predict.py里手动构造特征字典时列的顺序和训练时 CSV 的列顺序不一致sklearn按位置取特征顺序一乱结果就全错。解决训练时用joblib把X.columns一起保存预测时按保存的列顺序重排。我一般会在train.py末尾加一行joblib.dump(list(X.columns), models/feature_cols.pkl)预测前先加载这个列表再对齐。4.2 类别不平衡导致召回率极低现象模型准确率 0.98但攻击样本几乎全漏。原因正常请求远多于攻击请求模型学会了“全判正常”也能拿高准确率。解决训练时加class_weightbalanced或者用imblearn做 SMOTE 过采样。评估时别看准确率看classification_report里 label1 的召回率。如果召回率低于 0.85先补攻击样本再调权重。4.3 URL 编码未解码导致特征失效现象明明包含 OR 11的请求has_select和count_都是 0。原因URL 里的特殊字符被百分号编码了%27没被解码正则匹配不到。解决在extract_features开头加from urllib.parse import unquote对 url 和 body 先unquote再统计。注意解码两次可能引入新问题解一次就够。4.4 模型文件路径写死导致换目录就报错现象在源码根目录能跑换到别的路径就FileNotFoundError。原因代码里用了相对路径models/rf_model.pkl工作目录一变就找不到。解决用os.path.dirname(__file__)拼绝对路径或者统一在项目根目录下启动服务。我习惯在app.py开头加BASE_DIR os.path.dirname(os.path.abspath(__file__))所有路径都基于它拼。4.5 Flask 调试模式在生产环境暴露敏感信息现象接口报错时返回完整堆栈包含文件路径和代码片段。原因app.run(debugTrue)没关。解决演示时可以用debugTrue但对外提供服务前改成debugFalse并用gunicorn或waitress启动。如果只是本地跑通流程这条可以先记着等要部署时再改。5. 进阶用法用特征重要性反推检测边界与模型迭代跑通基础流程后别急着换更复杂的模型。先做一件事把随机森林输出的特征重要性排序打印出来看看哪些特征在主导判定。我一般会取前 15 个特征对照攻击类型逐条分析。比如count_和has_select排在前列说明模型主要靠 SQL 注入特征做判断如果url_len权重异常高可能是数据里长 URL 的攻击样本过多模型走了捷径。这一步能帮你判断模型是真的学到了攻击模式还是只是拟合了数据集的偏差。下面这段代码把特征重要性和攻击类型关联起来输出一个可读的表格。import joblib import pandas as pd clf joblib.load(models/rf_model.pkl) cols joblib.load(models/feature_cols.pkl) imp pd.DataFrame({ feature: cols, importance: clf.feature_importances_ }).sort_values(importance, ascendingFalse) # 按特征名归类到攻击类型 def category(name): if select in name or union in name or count_ in name: return SQL注入 if script in name or alert in name or count_ in name: return XSS if ../ in name or exec in name: return 路径遍历/命令注入 return 通用统计 imp[category] imp[feature].apply(category) print(imp.head(15).to_string(indexFalse)) print(\n按类别汇总重要性) print(imp.groupby(category)[importance].sum().sort_values(ascendingFalse))如果某个攻击类别的总重要性低于 0.05说明当前特征对这类攻击几乎没区分度模型大概率检测不到。这时候要做的不是换模型而是补特征。比如路径遍历检测弱就加count_../、has_etc_passwd、url_depth这几个特征重新训练后再看重要性变化。这个“训练-看重要性-补特征-再训练”的循环比盲目调参有效得多。另一个进阶方向是给预测结果加一个置信度阈值。默认predict返回 0 或 1但predict_proba能给出概率。你可以设一个阈值比如概率大于 0.8 才判攻击0.5 到 0.8 之间标记为“可疑”转人工。这样能降低误报对正常业务的影响。代码改动很小在predict.py里把clf.predict换成clf.predict_proba然后按阈值分支即可。最后说一个我踩过的坑有次我把模型准确率调到 0.99兴冲冲拿去演示结果老师随手输了一个带%00截断的请求系统判正常。回头查才发现训练数据里根本没有空字节截断的样本特征里也没有对应统计项。从那以后我每次交付前都强制走一遍“构造边界样本”的流程手工造一批编码变形、大小写混合、注释插入的请求跑一遍看漏报哪些再决定要不要补特征。这个习惯帮我省掉了好几次现场翻车。希望帮到你。本文还有配套的精品资源点击获取