简介:这份资源是面向高校学生与初学者的毕业设计/期末大作业级项目,主题为基于监督学习的Web入侵检测系统,适合需要完成课程设计、毕设或想入门机器学习安全应用的人群。压缩包共60个文件,约2.25MB,以ipynb实验笔记、txt样本与词表、html原始请求页面、py脚本为主,另含pkl/pickle模型文件与md说明,覆盖数据爬取、字符处理、去重、参数提取、模型保存等完整流程。已有145人学习下载。代码注释详尽,新手也能看懂,下载后简单部署即可运行,可直接作为高分项目参考。目录按功能模块划分清晰,包含正常与恶意请求样本、关键词列表及多份实验记录,便于理解监督学习在Web攻击识别中的特征工程与训练思路,也方便在此基础上二次修改与扩展。
1. 从一份毕业设计说起:监督学习怎么做 Web 入侵检测
很多同学做毕业设计时,第一反应是「找个开源 WAF 改一改」,结果发现规则库看不懂、日志格式对不上、答辩时被问「你的模型在哪」直接卡壳。基于监督学习的 Web 入侵检测系统,核心思路其实很朴素:把 HTTP 请求当成一条样本,用带标签的历史流量训练分类器,让模型自己学会区分正常请求和攻击请求。它解决的是传统规则匹配「写不完、跟不上、误报高」的问题,适合安全方向毕业生、刚接触 ML 的运维,以及想给现有站点加一层智能检测的开发者。这一章先把「监督学习 + Web 入侵检测」这条链路讲清楚,后面几章再落到源码结构、特征工程、模型训练和文档说明怎么写。
2. 系统架构与源码目录:先看懂再动手
2.1 为什么选监督学习而不是无监督
Web 入侵检测的公开数据集(如 CSIC 2010、ECML/PKDD 2007)都带正常/异常标签,这天然适合监督学习。无监督方法(如孤立森林)虽然不需要标注,但毕业设计答辩时很难解释「为什么这条被判为攻击」——你拿不出一个明确的决策边界。监督学习可以用准确率、召回率、F1 值量化,论文里好写,代码里好调。常见做法是先用随机森林或 XGBoost 跑基线,再用 LSTM 或 TextCNN 做序列特征对比,最后选一个综合指标最好的作为主模型。
选型时注意:Web 请求是短文本,字符级特征比词级更稳,因为攻击载荷里经常出现../、<script>、union select这类没有自然语言语义的片段。我一般会先做字符级 TF-IDF,再上 n-gram,最后才考虑深度学习。
2.2 源码目录应该长什么样
一个能跑通、能写进文档说明的工程,目录不要贪多,按职责分四层就够:
web_ids/ ├── data/ # 原始数据集与预处理后的特征文件 │ ├── raw/ # CSIC 2010 等原始数据 │ └── processed/ # 向量化后的 npz / csv ├── src/ │ ├── preprocess.py # 请求解析、URL 解码、特征提取 │ ├── features.py # TF-IDF / n-gram / 统计特征 │ ├── train.py # 模型训练与交叉验证 │ ├── evaluate.py # 混淆矩阵、ROC、指标输出 │ └── detect.py # 单条请求推理接口 ├── models/ # 保存的模型文件(joblib / h5) ├── configs/ │ └── config.yaml # 路径、超参数、阈值 ├── docs/ │ └── 文档说明.md # 环境、数据、训练、推理、指标 └── requirements.txt这个结构的好处是:preprocess.py和features.py分离,答辩时能说清「数据清洗」和「特征工程」是两件事;detect.py单独提供推理入口,方便演示。文档说明里要写清楚每个文件的作用、输入输出格式、运行顺序,不要只贴代码。
2.3 环境依赖与最小可运行配置
# Python 3.8+,建议用虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install numpy pandas scikit-learn xgboost joblib flask pyyaml # 如果要用深度学习模型,再加: pip install torch torchvision # 或 tensorflowrequirements.txt里锁定版本,比如scikit-learn==1.3.0,避免答辩现场因为版本差异跑不起来。配置文件config.yaml把路径和超参数抽出来:
data: raw_path: "data/raw/csic2010.txt" processed_path: "data/processed/features.npz" model: type: "random_forest" n_estimators: 200 max_depth: 20 test_size: 0.2 random_state: 42 detect: threshold: 0.5参数说明:n_estimators是树的数量,200 在 CSIC 数据集上已经能到 0.98 左右的准确率;max_depth控制过拟合,20 是经验值;threshold是判定为攻击的概率阈值,演示时可以调到 0.3 让攻击样本更容易被检出,但误报会上升。
3. 特征工程与模型训练:把 HTTP 请求变成向量
3.1 请求解析与标签对齐
CSIC 2010 的原始格式是「请求行 + 请求头 + 请求体」,正常请求和攻击请求混在一起。预处理第一步是把每条请求拆成方法、URL、参数、Body 四部分,然后统一做 URL 解码和大小写归一化。
import urllib.parse import re def parse_request(raw_line): """把一行原始请求解析成结构化字段""" parts = raw_line.strip().split(' ', 2) if len(parts) < 3: return None method, url, body = parts # URL 解码,攻击载荷里常见 %2e%2e%2f 这类编码 url_decoded = urllib.parse.unquote(url) body_decoded = urllib.parse.unquote(body) # 提取参数个数、特殊字符数等统计特征 special_chars = len(re.findall(r'[<>\'";()]', url_decoded + body_decoded)) return { 'method': method, 'url': url_decoded, 'body': body_decoded, 'url_len': len(url_decoded), 'body_len': len(body_decoded), 'special_count': special_chars, 'has_sql_keyword': int(bool(re.search(r'(union|select|insert|drop)', (url_decoded + body_decoded).lower()))) }逻辑说明:urllib.parse.unquote把%2e%2e%2f还原成../,否则模型学不到路径穿越特征。special_count统计特殊字符数量,SQL 注入和 XSS 载荷通常比正常请求多。has_sql_keyword是一个简单的规则特征,和 TF-IDF 互补。参数上,url_len和body_len对异常检测很有效,因为攻击载荷往往比正常参数长。
标签对齐时注意:CSIC 数据集里正常请求和攻击请求是分开的文件,合并后要打上label=0/1,并且打乱顺序,否则训练集和测试集分布不一致。
3.2 字符级 TF-IDF 与 n-gram 参数怎么设
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.model_selection import train_test_split import numpy as np def build_features(texts, labels): # 字符级 n-gram,ngram_range=(1,3) 覆盖单字符到三字符组合 vectorizer = TfidfVectorizer( analyzer='char', ngram_range=(1, 3), max_features=5000, min_df=2, sublinear_tf=True ) X = vectorizer.fit_transform(texts) y = np.array(labels) return X, y, vectorizer # 假设 texts 是 url + body 拼接后的字符串列表 X, y, vec = build_features(texts, labels) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y )参数说明:analyzer='char'是关键,Web 攻击载荷里'、<、/这些字符单独出现就有意义,词级分词会丢掉。ngram_range=(1,3)能捕捉../、<script>、union select这类片段,再大容易过拟合。max_features=5000在 CSIC 上足够,太多会拖慢训练。min_df=2过滤只出现一次的特征,降低噪声。sublinear_tf=True用对数缩放词频,避免高频字符主导。
如果要用统计特征和 TF-IDF 拼接,用scipy.sparse.hstack:
from scipy.sparse import hstack # stats_matrix 是 parse_request 输出的数值特征矩阵 X_combined = hstack([X, stats_matrix])注意稀疏矩阵拼接时维度要对齐,stats_matrix也要转成csr_matrix。
3.3 随机森林训练与交叉验证
from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score from sklearn.metrics import classification_report, confusion_matrix import joblib clf = RandomForestClassifier( n_estimators=200, max_depth=20, min_samples_split=5, class_weight='balanced', random_state=42, n_jobs=-1 ) # 5 折交叉验证,看模型稳定性 cv_scores = cross_val_score(clf, X_train, y_train, cv=5, scoring='f1') print(f"CV F1: {cv_scores.mean():.4f} (+/- {cv_scores.std():.4f})") clf.fit(X_train, y_train) y_pred = clf.predict(X_test) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred)) joblib.dump(clf, 'models/rf_model.pkl') joblib.dump(vec, 'models/tfidf_vectorizer.pkl')参数说明:class_weight='balanced'在攻击样本少于正常样本时很重要,否则模型会偏向多数类。min_samples_split=5防止过拟合。n_jobs=-1用满 CPU 核。交叉验证的 F1 均值如果低于 0.95,检查特征工程是否有漏,或者数据集标签是否对齐。
训练完把模型和向量化器一起保存,推理时必须用同一个向量化器,否则特征维度对不上。
3.4 推理接口与阈值调整
import joblib import numpy as np model = joblib.load('models/rf_model.pkl') vectorizer = joblib.load('models/tfidf_vectorizer.pkl') def detect(request_text, threshold=0.5): X = vectorizer.transform([request_text]) prob = model.predict_proba(X)[0][1] # 攻击类概率 return { 'is_attack': bool(prob >= threshold), 'probability': round(float(prob), 4) } # 演示 result = detect("GET /admin.php?id=1' OR '1'='1 HTTP/1.1") print(result) # {'is_attack': True, 'probability': 0.97}阈值threshold是演示时最常被问的参数。默认 0.5 平衡误报和漏报;如果答辩时想展示检出率,可以调到 0.3,但要说清「生产环境需要根据业务容忍度调整」。predict_proba返回的是概率,比predict的硬标签更有解释力。
4. 避坑与排查:那些答辩现场容易翻车的地方
4.1 现象:训练准确率 0.99,测试只有 0.6
原因:数据泄露。常见错误是在train_test_split之前就做了 TF-IDF 拟合,导致测试集的信息进入训练。解决:先划分训练集和测试集,再在训练集上fit_transform,测试集只transform。
# 正确顺序 X_train_text, X_test_text, y_train, y_test = train_test_split( texts, labels, test_size=0.2, random_state=42, stratify=labels ) vectorizer = TfidfVectorizer(analyzer='char', ngram_range=(1,3), max_features=5000) X_train = vectorizer.fit_transform(X_train_text) X_test = vectorizer.transform(X_test_text) # 只 transform4.2 现象:模型把正常请求判成攻击,误报高
原因:正常请求里也有select、script这类词,比如搜索框输入「select 教程」。解决:不要只用关键词规则,TF-IDF 的上下文 n-gram 能区分select单独出现和union select组合出现。另外可以加白名单机制,对已知正常参数放行。
4.3 现象:推理时报「特征维度不匹配」
原因:保存模型时用了max_features=5000,推理时重新fit了一个新的向量化器,维度可能变成 4800。解决:向量化器和模型必须一起保存、一起加载,推理时只调用transform,不要重新fit。
4.4 现象:CSIC 数据集加载后标签全是 0
原因:CSIC 2010 的正常请求和攻击请求在不同文件,合并时忘了给攻击文件打label=1。解决:写一个load_csic函数,分别读取两个文件,正常文件label=0,攻击文件label=1,然后pd.concat并sample(frac=1)打乱。
4.5 现象:答辩时被问「你的模型可解释性在哪」
原因:只报了准确率,没有特征重要性。解决:随机森林可以输出feature_importances_,取 Top 20 特征,对应回字符 n-gram,就能解释「模型主要看哪些字符组合」。
import numpy as np importances = clf.feature_importances_ feature_names = vectorizer.get_feature_names_out() top_idx = np.argsort(importances)[-20:][::-1] for i in top_idx: print(f"{feature_names[i]}: {importances[i]:.4f}")5. 文档说明怎么写才像一份能交付的工程文档
5.1 文档结构:从环境到指标,缺一不可
一份合格的文档说明至少包含六部分:环境依赖、数据说明、目录结构、训练步骤、推理步骤、指标结果。不要写成论文的「绪论」风格,直接按操作顺序写。比如「数据说明」里要写清楚 CSIC 2010 的下载来源、文件格式、样本数量、标签定义;「训练步骤」里写python src/train.py --config configs/config.yaml,并说明每个参数的含义。
5.2 指标表格与对比实验
文档里放一张对比表,把随机森林、XGBoost、TextCNN 的指标列出来,答辩时一目了然:
| 模型 | 准确率 | 召回率 | F1 | 训练耗时 |
|---|---|---|---|---|
| 随机森林 | 0.982 | 0.976 | 0.979 | 12s |
| XGBoost | 0.985 | 0.981 | 0.983 | 25s |
| TextCNN | 0.978 | 0.970 | 0.974 | 3min |
表格里的数字要来自你自己的evaluate.py输出,不要抄网上的。训练耗时也写上,体现工程意识。
5.3 一个容易被忽略的细节:推理演示脚本
文档最后附一个demo.py,读一条请求,输出检测结果和概率。答辩现场直接跑,比 PPT 截图有说服力。
# demo.py from src.detect import detect test_requests = [ "GET /index.html HTTP/1.1", "GET /admin.php?id=1' OR '1'='1 HTTP/1.1", "POST /comment?text=<script>alert(1)</script> HTTP/1.1" ] for req in test_requests: print(req[:50], '->', detect(req, threshold=0.5))这个脚本要能在 10 秒内跑完,依赖越少越好。如果模型加载慢,可以在启动时预加载,不要每次调用都joblib.load。
5.4 版本控制与可复现性
把requirements.txt、config.yaml、models/下的模型文件一起提交,但模型文件如果太大(超过 100MB),可以只提交训练脚本和随机种子,文档里写清楚「运行train.py可复现模型」。随机种子固定random_state=42,保证每次训练结果一致。
我自己的习惯是:答辩前一周,把整个工程拷到一台干净机器上,从pip install开始跑一遍,把遇到的每个报错都记下来,补进文档的「常见问题」里。这样现场不管谁问,你都有答案。希望帮到你。
本文还有配套的精品资源,点击获取