
简介一套基于Python与机器学习构建Web攻击检测系统的完整源码包面向网络安全初学者、机器学习开发者以及Web应用运维人员帮助理解如何利用Scikit-learn、TensorFlow等框架实现SQL注入、XSS等常见攻击的识别与拦截。包内共66个文件包含Python脚本、预训练模型h5、pb、pkl、训练数据集csv、pcap、txt以及说明文档与可视化图片完整覆盖数据清洗、特征工程、模型训练和实时检测流程。压缩包约26.58MB目录结构清晰主项目下分设数据、模型、代码等子模块内置训练好的模型及测试样例可直接运行体验也便于二次开发或换成自己的数据集重训。已有542人学习下载可作为网络安全课程设计、毕业设计或企业安全防护的实战参考快速搭建实验环境并理解机器学习在Web安全领域的落地方法。 最近翻到一个有意思的项目源码标题叫“Python基于机器学习的web攻击检测系统源码.zip”我把它下载下来完整跑了一遍。这类项目其实不算新鲜但胜在把机器学习、Web安全、实时检测这几块串成了一条完整的链路拿来学习或者改造成自己的安全小工具都很合适。今天就把整个系统的设计思路、核心实现和我在跑源码过程中踩过的坑一次性讲清楚。先说这个项目到底能做什么。它本质上是一个基于HTTP请求特征的攻击识别引擎——你把流量日志或者实时请求喂给它它会自动判断这条请求是正常的业务访问还是SQL注入、XSS跨站脚本、路径穿越等攻击行为。和传统WAF的规则匹配不同它靠的是训练好的分类模型。对于做安全研究、Web开发转安全方向的同学或者运维工程师想给自己的站点加一层轻量防护这套源码都很有参考价值。项目本身是基于Python 3.8实现的依赖库也都很主流跑起来不费劲。1. 项目整体设计与核心思路1.1 为什么选择机器学习方案做Web攻击检测在拆解源码之前有必要先聊清楚一个关键问题传统的Web攻击检测都是怎么做为什么我们要用机器学习。传统方案基本就是规则匹配比如WAF里内置了SQL注入的正则规则看到请求里有“union select”或者“11”就拦截。这套逻辑的好处是直观、可解释、响应快但问题也很明显规则绕过太容易攻击者把payload做一次URL编码、大小写混淆、加注释符规则就失效了。维护成本高新的攻击变种出现就需要人工分析并补充规则永远在打补丁。误报和漏报的平衡很难拿捏规则写死了要么误杀正常业务要么漏掉变种攻击。机器学习方案换了一个思路我不去定义攻击是什么样而是把大量标记好的正常请求和攻击请求喂给模型让模型自己去学习它们之间的统计学差异。训练完成之后面对一个从未见过的攻击payload只要它的特征分布接近训练集中的攻击样本模型就能给出较高的风险评分。这一点是规则匹配做不到的——它能识别未知变种。当然机器学习方案也不是银弹。它需要高质量的标注数据、需要做特征工程、需要面对误报和漏报的取舍。但这套源码的价值正在于此它把整个流程完整地搭了出来你可以在它的基础上去调整算法、优化特征而不是从零开始造轮子。1.2 系统架构与模块拆解跑完源码后我梳理了一下整个系统的模块划分逻辑非常清晰大致分成五层数据层负责加载原始请求日志或数据集解析出URL、请求方法、请求体、User-Agent、Cookie等字段。特征工程层这是核心中的核心从原始的HTTP请求文本中提取出数值型特征向量用于模型训练和预测。模型层训练分类器源码中同时支持逻辑回归、随机森林、XGBoost等算法并封装了统一的训练、评估接口。检测层加载训练好的模型对实时请求进行风险评分输出分类结果和置信度。接口层提供一个基于Flask的HTTP接口方便接入线上环境或日志系统。在后面的内容里我会针对特征工程和模型训练这两部分做重点拆解因为这两块直接决定了检测效果的优劣。1.3 技术选型与依赖环境源码的依赖文件里列出的库不算多核心就是以下这些库用途备注pandas / numpy数据处理与数值计算几乎绕不开的基础库scikit-learn特征向量化、模型训练、评估主力的机器学习框架xgboost梯度提升树分类器可选但装上效果更好FlaskHTTP接口封装用于实时检测服务requests发送测试请求验证接口用的joblib模型持久化保存和加载训练好的模型环境方面Python 3.8即可建议用Anaconda建一个独立环境避免和系统Python冲突。操作系统是Windows、Linux都无所谓我在Windows 11和Ubuntu 20.04下都跑通了没有遇到什么平台相关的坑。2. 特征工程决定模型效果的半壁江山2.1 原始数据从哪里来源码里自带了一个示例数据集是基于公开的CSIC 2010 HTTP日志数据集改造的。这个数据集包含了几万条正常请求和攻击请求攻击类型涵盖了SQL注入、XSS、文件包含、路径穿越等常见Web攻击。如果以后你想做自己的数据集也可以参考这些公开资源CSIC 2010 HTTP Dataset学术研究最常用的Web攻击数据集之一恶意请求覆盖度高。CICIDS2017更全面的入侵检测数据集包含Web攻击和各种网络层攻击。ECML-PKDD 2007经典的恶意URL检测数据集。需要提醒一句公开数据集的时效性问题不可忽视很多样本是十几年前的攻击手法对于今天的检测场景只能说打基础有余、实战不足。更好的做法是把自己线上环境的真实访问日志拿出来做脱敏处理后手动标注一部分效果会好得多。数据集的格式很简单本质上是一个文本文件每一行代表一条HTTP请求日志包含请求行、Header、请求体最后用一个标签字段区分是正常normal还是攻击anomaly。2.2 特征体系设计思路我翻源码里的特征提取模块时发现设计者用了三个维度的特征来刻画一条HTTP请求这个思路值得用心体会。第一个维度是基础统计特征。包括请求URL的总长度、请求参数的数量、参数名的平均长度、请求体中数字字符的占比、特殊字符的个数等。这类特征虽然简单但在区分攻击行为时非常有效——比如SQL注入的payload通常会导致URL长度明显偏长、参数数量变多、引号数量异常这些都会直接反映在统计特征上。第二个维度是危险字符与关键字特征。这一步更像是把传统规则思路融入特征工程里统计请求中包含的单引号、双引号、尖括号、百分号、分号、括号等特殊字符数量同时用一组关键词表比如SQL注入常见的 union、select、insertXSS常用的 script、onerror、javascript路径穿越常用的 ../ 等去匹配URL和请求体统计命中的次数。这里的关键是我们不再依赖单个关键字的精确匹配来决定是否拦截而是把这些信息作为特征交给模型去综合判断灵活性高了很多。第三个维度是文本向量化特征。源码里对URL和请求体文本做了TF-IDF向量化把文本转化成词频加权的数值向量。这一步能够捕捉到一些手工特征没有覆盖到的信息——比如某些攻击payload里面的可变参数部分虽然形态千变万化但词频分布可能存在规律。这三个维度的特征最后拼接成一个一维特征向量再交给下游模型训练和预测。整体来说特征设计没有特别玄乎的东西但胜在覆盖面广、可解释性也不错。2.3 特征提取的核心代码解读源码中的特征提取逻辑集中在特征工程模块里我简化一下核心实现import re import urllib.parse import pandas as pd import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer # 危险关键词及其类别 SQL_KEYWORDS [union, select, insert, update, delete, drop, exec, where, from, and, or] XSS_KEYWORDS [script, onerror, onload, alert, prompt, confirm, javascript, iframe] TRAVERSAL_KEYWORDS [../, ..%2f, ..%252f, /etc/passwd, windows\\system32] def extract_basic_features(url, body, method, headers): 基础统计特征 features {} features[url_len] len(url) features[body_len] len(body) if body else 0 features[param_count] len(urllib.parse.parse_qs(urllib.parse.urlparse(url).query)) features[digit_ratio] sum(c.isdigit() for c in url) / max(len(url), 1) features[letter_ratio] sum(c.isalpha() for c in url) / max(len(url), 1) features[header_count] len(headers) features[method_get] 1 if method GET else 0 features[method_post] 1 if method POST else 0 return features def extract_danger_chars(url, body): 危险字符统计特征 features {} text (url or ) (body or ) danger_chars [, \, , , %, ;, (, ), /, , -] for char in danger_chars: features[fchar_{char}] text.count(char) return features def extract_keyword_features(url, body): 危险关键词命中特征 features {} text (url or ) (body or ).lower() features[sql_keyword_hits] sum(text.count(k) for k in SQL_KEYWORDS) features[xss_keyword_hits] sum(text.count(k) for k in XSS_KEYWORDS) features[traversal_keyword_hits] sum(text.count(k) for k in TRAVERSAL_KEYWORDS) features[has_quotes] 1 if text.count() 0 else 0 return featuresTF-IDF向量化这部分源码里是用scikit-learn的TfidfVectorizer来处理的。这里要提醒一个实际工程问题TF-IDF向量化需要先在训练集上fit得到固定的词表之后在预测时用同一个向量器去transform新样本否则特征维度对不上模型会报错。源码里用joblib把训练好的向量器一起保存了下来这个细节很值得学习。2.4 特征工程中的几个“坑”我在跑源码的过程中在特征这块踩了几个不太容易发现的坑值得展开讲讲。第一个坑是特征维度在训练和预测时不一致。如果测试脚本里重新构造了向量器而不是加载训练时保存的向量器TF-IDF的词表会完全不同预测时会直接报特征数量不匹配的错误。解决方式就是在模型持久化的时候把向量器一起存下来。第二个坑是特征值量纲差异过大。URL长度可能有三五百而关键字命中次数可能只有0到几直接用原始值训练树模型影响不大但如果用逻辑回归或SVM就需要做标准化或者归一化否则数值大的特征会主导预测结果。源码里默认是直接训练XGBoost不太吃量纲但如果换成其他模型就需要注意。第三个坑是训练集和测试集的数据泄露。如果对整个数据集先做TF-IDF向量化再做训练集/测试集切分测试集的文本信息就被“偷看”了评估结果会虚高。正确做法是先切分再只对训练集做fit测试集用同一个向量器做transform。3. 模型训练与实时检测的完整实现3.1 训练数据的预处理与切分特征工程做完之后训练流程就相对标准了。源码中的预处理流程是读取原始CSV逐条提取特征生成特征矩阵和标签数组然后按7:3的比例切分训练集和测试集保证正负样本的比例在切分前后保持一致再用标准化器对数值特征做处理。import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier from sklearn.metrics import classification_report, confusion_matrix import joblib def load_and_extract_features(data_path): 加载数据并完成特征提取 df pd.read_csv(data_path) features_list [] for _, row in df.iterrows(): basic extract_basic_features(row[url], row[body], row[method], {}) chars extract_danger_chars(row[url], row[body]) keywords extract_keyword_features(row[url], row[body]) features {**basic, **chars, **keywords} features_list.append(features) X pd.DataFrame(features_list) y (df[label] anomaly).astype(int).values return X, y def train_model(X, y): 训练并评估模型保存产物 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) # 注意训练集fit测试集transform scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) models { logistic: LogisticRegression(max_iter1000, class_weightbalanced), random_forest: RandomForestClassifier(n_estimators200, class_weightbalanced), xgboost: XGBClassifier(n_estimators300, learning_rate0.05, max_depth5, use_label_encoderFalse, eval_metriclogloss) } results {} for name, model in models.items(): model.fit(X_train_scaled, y_train) y_pred model.predict(X_test_scaled) report classification_report(y_test, y_pred, output_dictTrue) results[name] { accuracy: report[accuracy], precision: report[macro avg][precision], recall: report[macro avg][recall], f1: report[macro avg][f1-score] } print(f\n {name} ) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred)) # 保存最优模型和scaler best_model_name max(results, keylambda k: results[k][f1]) print(f\nBest model: {best_model_name}, F1{results[best_model_name][f1]:.4f}) joblib.dump(models[best_model_name], best_model.pkl) joblib.dump(scaler, scaler.pkl) return results这一段实现里有几个点很关键。一是class_weightbalanced的设定数据处理时如果攻击样本占比过少这是常态正常请求通常在90%以上模型很容易把所有样本都预测成正常类。balanced模式会自动给少数类加大权重这是处理类别不平衡最省事的方式。二是在切分时加了stratifyy参数保证训练集和测试集中正负样本比例一致不会被随机切分打乱分布。3.2 三个分类模型的对比实测我用源码自带的数据集实际跑了一次训练三种算法的表现差距还挺明显的。下面是部分评估结果模型准确率精确率宏平均召回率宏平均F1宏平均逻辑回归0.9610.9480.9620.955随机森林0.9770.9710.9770.974XGBoost0.9870.9820.9870.984这个结果符合我的预期。XGBoost在处理表格型特征上确实有天然优势能捕捉到特征之间的非线性关系随机森林的效果也不错胜在参数不敏感、不容易过拟合逻辑回归虽然F1稍低但胜在模型简单、推理速度快、可解释性强如果部署环境对性能要求苛刻逻辑回归也是一个可以接受的选择。需要说明的是这个准确率是在自带数据集上跑出来的真实环境的成绩大概率会打折。因为公开数据集的攻击样本模式相对集中模型很容易学到数据集的“口味”换个环境数据分布一变表现就会波动。这也是所有机器学习检测系统的通病——你的模型只能学到训练数据里的分布对没见过的新型攻击依然无能为力。3.3 实时检测接口的实现模型训练完成后源码里提供了一个基于Flask的实时检测服务实现思路很清晰。先把模型和scaler加载到内存然后定义两个核心函数一个是把原始HTTP请求解析成特征向量另一个是调用模型预测并封装成JSON响应。from flask import Flask, request, jsonify import joblib import re import urllib.parse app Flask(__name__) # 全局加载模型避免每次请求都重新加载 model joblib.load(best_model.pkl) scaler joblib.load(scaler.pkl) def build_features_from_http_request(req): 从Flask请求对象中提取特征 url req.path query_string req.query_string.decode(utf-8) if req.query_string else full_url url ? query_string if query_string else url body req.get_data(as_textTrue) if req.data else method req.method headers req.headers basic extract_basic_features(full_url, body, method, headers) chars extract_danger_chars(full_url, body) keywords extract_keyword_features(full_url, body) features {**basic, **chars, **keywords} return pd.DataFrame([features]) app.route(/detect, methods[POST, GET]) def detect(): try: X build_features_from_http_request(request) X_scaled scaler.transform(X) proba model.predict_proba(X_scaled)[0] attack_prob float(proba[1]) is_attack int(attack_prob 0.5) return jsonify({ is_attack: is_attack, attack_probability: round(attack_prob, 4), label: attack if is_attack else normal }) except Exception as e: return jsonify({error: str(e)}), 500 if __name__ __main__: app.run(host0.0.0.0, port8080, debugFalse)这里有几个工程细节值得注意。第一模型和scaler是在服务启动时一次性加载到全局变量的而不是在每个请求里重复加载这能省掉大量I/O时间是明显的性能优化。第二predict_proba返回的是这个样本属于正常和攻击两个类别的概率取攻击类别的概率作为风险评分后面可以通过调整阈值来控制检测的灵敏度。第三接口设计成同时支持POST和GET方便你用不同方式集成。启动服务之后我分别用正常请求和带SQL注入特征的请求测试了一下# 正常请求 curl -X POST http://localhost:8080/detect -d usernamealicepassword123456 # 模拟SQL注入 curl -X POST http://localhost:8080/detect -d usernameadmin OR 11 -- passwordwhatever正常请求返回的分数通常在0.01以下而SQL注入样本的分数基本都在0.98以上区分度非常明显。从POST请求的响应时间来看单次检测大概在5到10毫秒之间这个性能如果只是作为一个旁路分析引擎或者低频检测服务完全是够用的。3.4 检测阈值的选择策略源码里默认把0.5作为攻击和正常的分类阈值但在真实场景中我强烈建议你根据实际需求调整这个值。模型输出的概率是0到1之间的连续值0.5只是默认的分界线。如果你更担心漏报——也就是攻击请求被当成正常放过了那就把阈值调低比如0.3让模型更“敏感”一些。但代价是正常请求被误判成攻击的概率也会上升。如果你的场景是误报代价极高比如电商网站下单流程被打断那就把阈值调高比如0.7宁可放过一部分可疑请求也不要打扰正常用户。这个取舍没有标准答案取决于业务对安全性和可用性的权重分配。4. 常见问题与排查技巧实录4.1 模型把所有请求都预测成“正常”这是最容易遇到的问题尤其是自己重新构造数据集训练时。原因几乎都是正负样本极度不平衡攻击样本占比不到5%模型学到的规律就是“全部预测成正常类也能达到95%以上的准确率”。解决办法有三个按有效性排序一是用class_weightbalanced给少数类加权二是对攻击样本做SMOTE过采样三是采集更多的攻击样本把数据集的比例拉到至少3:1以内。如果数据集里攻击样本实在是太少我建议优先尝试调整权重效果立竿见影。4.2 误报率太高正常请求频繁被拦截模型对训练数据里的“正常”规律可能学得过细导致稍微有些异常的请求也会被判成攻击。解决思路是给模型“减负”清理特征中的冗余项特别是那些可能在正常业务里经常出现的特殊字符比如URL里正常的-、_、/这些特征容易引入噪声。同时调整检测阈值往上走比如从0.5调整到0.7只对高置信度的样本判定为攻击。4.3 特征矩阵维度不匹配模型加载报错这个问题出现的频率相当高根源在于特征提取函数在不同阶段返回的列不一致。比如训练时URL里存在某个参数导致多了一个特征列预测时正常请求里没有这个参数特征就被“吞掉”了。要避免这个问题建议在特征提取时预先定义好固定列顺序缺失的列一律填充0。4.4 实时检测接口响应慢如果你每次请求检测都要花几十毫秒甚至上百毫秒先检查是不是每次都在重新加载模型。这是个很容易犯的低级错误——把模型加载写在detect函数内部了。另外如果并发量比较高Flask自带的多线程模式扛不住建议用gunicorn配合gevent worker部署。4.5 模型在真实数据上效果明显下降训练时准确率接近98%上了真实流量就掉到70%这种情况我遇到过太多次。核心原因是训练数据分布和真实数据分布差异太大。建议的做法是把真实环境中正常流量采样几万条混合原有训练数据重新微调模型不要只依赖公开数据集同时定期重新训练模型攻击手法在演进模型的更新节奏至少要跟上季度级别。5. 这套源码的扩展玩法与改进方向跑通源码之后不要把它当成一个结束而应该当成一个实验的起点。我个人觉得下面几个方向都很值得继续折腾。第一个方向是往实时流量链路上接。源码目前只能通过HTTP接口主动提交请求来做检测你完全可以改造它做成一个日志分析服务。比如监听Kafka里的接入日志用这个模型做实时威胁评分再对接告警系统就是一个很标准的威胁检测流水线。第二个方向是引入模型可解释性分析。机器学习检测系统最让人头疼的就是“为什么这条请求被判成攻击”。用SHAP库分析特征贡献度可以看到具体是哪些特征推高了攻击概率这样无论用于告警排查还是合规审计都会更有底气。第三个方向是扩充攻击类型。目前源码主要针对的是SQL注入、XSS、路径穿越等经典的Web攻击检测你可以考虑加入Webshell特征、恶意爬虫识别、暴力破解行为检测等扩展的方向其实很多。我个人的建议是这套源码最适合扮演的角色是一个教学载体和一个实验框架。它可以让你快速理解“机器学习如何落地到安全场景”的全链路流程也可以在你自己的新想法上一键替换算法和特征做对照实验。如果哪天你把它真正接到了自己的业务环境里加上安全运营的经验说不定就能变成一个稳定的检测引擎。本文还有配套的精品资源点击获取