简介:这是一套面向计算机专业学生与安全初学者的机器学习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[f"count_{ch}"] = full.count(ch) # 敏感关键词命中 keywords = ["select", "union", "script", "alert", "../", "exec", "drop"] lower = full.lower() for kw in keywords: features[f"has_{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?id=1' OR '1'='1", "") for k, v in sample.items(): print(f"{k}: {v}")这段代码的逻辑很直白:把请求拼成一个字符串,然后统计长度、特殊字符、关键词和熵。参数说明上,url_len和body_len反映请求规模,异常长的 URL 往往是注入或溢出尝试;count_*系列对 SQL 注入和 XSS 特别敏感;has_*是布尔特征,命中就置 1;entropy越高说明字符串越杂乱,可能是编码后的攻击载荷。你拿到源码后,可以先用几条正常请求和几条带' OR 1=1的请求跑一遍,看看特征值差异是否明显。如果差异不大,说明特征设计有问题,需要加特征或换提取方式。
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_size=0.2, random_state=42, stratify=y ) clf = RandomForestClassifier( n_estimators=100, # 树的数量,越多越稳但越慢 max_depth=None, # 不限制深度,让树充分生长 min_samples_split=2, # 节点分裂所需最小样本数 class_weight="balanced", # 攻击样本少时自动加权 random_state=42 ) 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_), key=lambda x: -x[1])[:10]: print(f"{name}: {imp:.4f}")参数上,n_estimators设 100 是常见起点,数据量上万后可以加到 200 到 300;class_weight="balanced"在攻击样本远少于正常样本时很关键,不加的话模型会倾向于全判正常;stratify=y保证划分后两类比例一致。跑完训练后,重点看召回率而不是准确率,因为漏报一个攻击比误报一个正常请求代价更高。如果召回率低于 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 flask==2.2.5 pip install scikit-learn==1.2.2 pip install pandas==1.5.3 pip install numpy==1.23.5 pip install joblib==1.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", encoding="utf-8", errors="ignore") 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_index=True) df.to_csv("data/processed/features.csv", index=False) 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?id=1%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.95,Web 接口调用却把所有请求都判成正常。原因:predict.py里手动构造特征字典时,列的顺序和训练时 CSV 的列顺序不一致,sklearn按位置取特征,顺序一乱结果就全错。解决:训练时用joblib把X.columns一起保存,预测时按保存的列顺序重排。我一般会在train.py末尾加一行joblib.dump(list(X.columns), "models/feature_cols.pkl"),预测前先加载这个列表再对齐。
4.2 类别不平衡导致召回率极低
现象:模型准确率 0.98,但攻击样本几乎全漏。原因:正常请求远多于攻击请求,模型学会了“全判正常”也能拿高准确率。解决:训练时加class_weight="balanced",或者用imblearn做 SMOTE 过采样。评估时别看准确率,看classification_report里 label=1 的召回率。如果召回率低于 0.85,先补攻击样本,再调权重。
4.3 URL 编码未解码导致特征失效
现象:明明包含' OR 1=1的请求,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(debug=True)没关。解决:演示时可以用debug=True,但对外提供服务前改成debug=False,并用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", ascending=False) # 按特征名归类到攻击类型 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(index=False)) print("\n按类别汇总重要性:") print(imp.groupby("category")["importance"].sum().sort_values(ascending=False))如果某个攻击类别的总重要性低于 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截断的请求,系统判正常。回头查才发现训练数据里根本没有空字节截断的样本,特征里也没有对应统计项。从那以后我每次交付前都强制走一遍“构造边界样本”的流程:手工造一批编码变形、大小写混合、注释插入的请求,跑一遍看漏报哪些,再决定要不要补特征。这个习惯帮我省掉了好几次现场翻车。希望帮到你。
本文还有配套的精品资源,点击获取