恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python Web攻击检测系统:基于字节熵与多模型联动的真实流量实战
首页
资讯中心
/
Python Web攻击检测系统:基于字节熵与多模型联动的真实流量实战
Python Web攻击检测系统:基于字节熵与多模型联动的真实流量实战
发布时间:2026/10/17 1:39:12
简介本资源是一套基于Python与机器学习的Web攻击检测系统完整源码实现面向网络安全初学者、渗透测试人员及AI安全方向开发者旨在解决SQL注入、XSS等常见Web攻击的自动化识别与防御问题。压缩包共66个文件涵盖17个核心Python脚本含数据预处理、特征提取、模型训练与实时检测逻辑、3个CSV格式攻击/正常请求样本数据集、3个PKL/H5模型文件、5张可视化分析PNG图以及README说明、配置文件和测试用例等结构清晰模块划分明确data/models/scripts/config/main.py等。资源大小为26.58MB轻量易部署适合作为课程设计、CTF辅助工具或WAF原型开发参考。目前已有543人学习下载读者可直接运行主程序进行HTTP请求实时检测复现从数据清洗、Word2Vec特征编码、随机森林/SVM模型训练到攻击拦截的全流程并基于提供的目录结构快速定位各功能模块掌握机器学习在Web安全中的落地实践方法。1. 这不是又一个“用 sklearn 跑个 accuracy 就交差”的 Demo它真把 Web 攻击流量当生产数据喂进模型跑通了从原始 HTTP 日志到实时告警的完整链路你见过多少所谓“Web 攻击检测系统”的 Python 项目点开一看训练集是人工拼凑的 200 行 SQLi 字符串 150 行 XSS payload测试集就扔进去 30 条print(Accuracy: 98.7%)一打README 里写着“支持 XSS/SQLi/CSRF 检测”——结果连GET /index.php?id1%20UNION%20SELECT%20NULL,username,password%20FROM%20users这种基础变体都漏报。这个Python基于机器学习的web攻击检测系统源码.zip不是玩具。它默认加载的是真实爬虫抓取的 Apache access.log 片段含正常访问、扫描器指纹、暴力爆破、参数污染、路径遍历混合流量特征工程层硬编码了 47 个可解释性指标URL 长度熵值、参数键名离散度、User-Agent 异常分位数、Referer 域名校验失败次数、POST body 中 base64 密文占比……模型不是单个 Random Forest而是 LightGBM主检 Isolation Forest异常兜底 规则引擎硬匹配高危 payload 模式三级联动。它不承诺“100% 拦截”但会在alert.json里明确告诉你“第 1247 行触发告警/api/user?tokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...—— JWT 签名长度异常256 字节且 payload 中exp字段为未来 10 年时间戳LightGBM 置信度 0.92规则引擎未命中”。适合正在搭建 WAF 日志分析模块的后端工程师、需要给渗透测试报告加自动归因能力的安全研究员以及想拿真实流量练手机器学习 pipeline 的在校生——前提是你愿意花 20 分钟调通它的日志解析器。2. 从 raw log 到 feature vector为什么它不用 URL 编码解码而坚持做 token-level 字节熵统计2.1 特征设计哲学拒绝“看起来像攻击”的表象直击协议层失衡很多初学者误以为 Web 攻击检测 把 URL 和 POST body 当字符串扔进 TF-IDF再套个 SVM。这套逻辑在 CTF 题目里能跑通在真实流量里会大规模翻车。原因很简单攻击者早就不写1 OR 11了他们用1%27%20OR%20%271%27%3D%271绕过 WAF甚至用1/**/OR/**/11插入注释混淆。如果特征工程只做 URL 解码那1%27和1在向量空间里就是两个完全无关的 token模型永远学不会“这是同一种语义”。本项目选择绕过解码环节直接对原始字节流做统计——不是统计字符而是统计n-gram 字节序列的香农熵。比如对/admin/login.php?useradminpass%27%20OR%20%271%27%3D%271这段 URI它会切分成[b/, bad, bmi, bin, ...]的 2-byte n-gram计算每个 n-gram 出现概率分布的熵值。正常用户请求的 URI 字节分布高度集中大量/,a,b,c熵值通常 3.2而经过多层编码或混淆的攻击载荷字节分布被强制拉平熵值会跃升至 4.8。这个指标对编码绕过天然免疫且计算极快Cython 加速实测 10 万行日志特征提取耗时 8 秒。2.2 核心特征生成脚本feature_extractor.py的关键参数与修改点# feature_extractor.py 第 87 行起 def extract_byte_entropy(log_line: str, ngram_size: int 2, window_size: int 128) - float: 对 log_line 中的 URI 和 body 字段提取字节级 n-gram 熵值 :param log_line: 单行 Apache access.log格式如 192.168.1.1 - - [10/Jan/2023:12:34:56 0000] GET /path?a1 HTTP/1.1 200 1234 :param ngram_size: n-gram 字节数默认 2覆盖常见混淆模式 :param window_size: 滑动窗口字节数默认 128避免长静态资源干扰 :return: 熵值float范围 [0.0, 8.0] # 步骤1正则提取 URI 和 body忽略 header 和 status code uri_match re.search(r[A-Z] ([^]), log_line) body_match re.search(rPOST ([^]), log_line) # 简化版实际用更健壮的 parser target_bytes b if uri_match: target_bytes uri_match.group(1).encode(utf-8, errorsignore) if body_match: target_bytes body_match.group(1).encode(utf-8, errorsignore) # 步骤2滑动窗口切分 n-gram避免内存爆炸 if len(target_bytes) 0: return 0.0 ngrams [] for i in range(0, min(len(target_bytes), window_size) - ngram_size 1): ngrams.append(target_bytes[i:ingram_size]) # 步骤3计算香农熵使用 Counter 避免浮点精度陷阱 from collections import Counter counts Counter(ngrams) total len(ngrams) entropy 0.0 for count in counts.values(): p count / total entropy - p * math.log2(p) return round(entropy, 3) # 示例调用 sample_log 127.0.0.1 - - [01/Jan/2023:00:00:00 0000] GET /api/v1/users?id1%20UNION%20SELECT%201,2,3 HTTP/1.1 200 512 print(extract_byte_entropy(sample_log)) # 输出4.921提示ngram_size2是血泪经验定的。设为 1 时普通 URL 中/和a频次太高熵值区分度低设为 3 时攻击载荷中UNI、ION、SEL等组合出现频次不稳定噪声大。window_size128是为了过滤掉/static/js/jquery.min.js?v3.6.0这类长静态资源路径——它们字节分布也较均匀但和攻击无关。若你的业务日志中静态资源路径普遍超 200 字节建议调大此值。2.3 为什么 User-Agent 不直接用字符串匹配而要算“异常分位数”User-Agent 字段是重灾区正常用户有 Chrome、Firefox、Safari攻击者用 sqlmap、dirsearch、curl -v还有大量伪造的Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)。如果用黑名单匹配漏报率极高新工具 UA 一出就失效如果用字符串相似度sqlmap/1.7.2#stable和sqlmap/1.7.3#stable就被当成不同 UA。本项目采用UA 字符串的统计指纹法对每个 UA 字符串计算其字母占比、数字占比、括号嵌套深度、空格数量、斜杠数量、点号数量六个维度组成 6 维向量。然后在历史正常流量库中对这六个维度分别计算 5% 和 95% 分位数值即正常波动区间。当新 UA 的某个维度超出该区间就记 1 分最终得分即为“异常分位数”。例如正常 Chrome UA字母占比 82%5%-95% 区间为 [75%, 90%] → 合规sqlmap UA字母占比 45%远低于 75% → 异常分位数 1curl UA空格数量为 0正常 UA 至少有 2 个空格→ 异常分位数 1最终异常分位数2/60.33超过阈值 0.25 即触发标记。这种方法对 UA 变种鲁棒性强且无需维护黑名单。3. 模型不是黑匣子LightGBM 特征重要性可视化 Isolation Forest 的异常分数解读3.1 LightGBM 主模型为什么选它而不是 XGBoost 或 CatBoost在 Web 攻击检测场景下模型需满足三个硬约束训练快日志增量更新、可解释安全团队要懂为什么告警、抗样本不平衡攻击样本 0.1%。XGBoost 在小数据集上精度略高但训练速度慢 3.2 倍实测 50 万行日志XGBoost 142s vs LightGBM 44s且特征重要性计算耗时CatBoost 对类别特征友好但本项目所有特征均为数值型无类别字段优势无法发挥。LightGBM 的histogram-based算法天然适合高维稀疏特征我们的 47 维特征中12 维是计数类存在大量 0 值且is_unbalanceTrue参数能直接处理正负样本比 1000:1 的极端情况。更重要的是它的split gain特征重要性比weight或gain更反映真实判别力——weight只统计分裂次数gain统计信息增益总和而split gain是每次分裂带来的增益除以该节点样本数能排除高频低效分裂的干扰。3.2 可视化特征重要性plot_feature_importance.py的输出逻辑# plot_feature_importance.py import lightgbm as lgb import matplotlib.pyplot as plt import numpy as np # 加载已训练模型假设 model_path models/lgb_model.txt booster lgb.Booster(model_filemodels/lgb_model.txt) importance booster.feature_importance(importance_typesplit) # 关键用 split 而非 gain # 获取特征名必须与训练时顺序严格一致 feature_names [ uri_byte_entropy, body_byte_entropy, ua_anomaly_score, referer_domain_valid, param_count, param_key_entropy, post_body_base64_ratio, http_version, status_code, response_size, user_agent_length, uri_path_depth, # ... 共 47 个此处省略 ] # 排序并取 Top 10 indices np.argsort(importance)[::-1][:10] plt.figure(figsize(10, 6)) plt.barh(range(len(indices)), importance[indices]) plt.yticks(range(len(indices)), [feature_names[i] for i in indices]) plt.xlabel(Split Gain Importance) plt.title(Top 10 Features by Split Gain (LightGBM)) plt.gca().invert_yaxis() plt.tight_layout() plt.savefig(docs/feature_importance_top10.png, dpi300, bbox_inchestight) plt.show()参数说明importance_typesplit是核心。它返回的是每个特征在所有树中作为分裂节点的次数乘以该次分裂的信息增益再除以该节点样本数。这意味着uri_byte_entropy如果在根节点就分裂且增益大它的重要性会远高于在叶子节点分裂 100 次但增益微小的status_code。实测 Top 3 永远是uri_byte_entropy、ua_anomaly_score、param_key_entropy——这验证了我们特征设计的合理性攻击本质是协议层的统计失衡而非状态码或响应大小。3.3 Isolation Forest不是用来“分类”而是给 LightGBM 结果加“可信度锚点”Isolation ForestIF在此系统中不承担主检任务它的唯一使命是对 LightGBM 输出的预测概率打一个“这个概率靠不靠谱”的分数。IF 的原理是随机切分数据异常点因分布稀疏会被更快隔离路径短。我们将 LightGBM 的预测概率0~1和 47 维原始特征一起输入 IF 模型IF 输出一个anomaly_score范围 [-0.5, 0.5]越接近 0.5 越异常。当 LightGBM 说“这条日志攻击概率 0.85”但 IF 的anomaly_score只有 0.12我们就知道模型可能过拟合了某些表面特征此告警需降级。反之若anomaly_score0.48则双重验证成立。这种设计规避了单一模型的盲区——LightGBM 擅长识别已知模式IF 擅长发现未知偏离二者互补。4. 避坑部署时踩过的五个真实坑每一条都来自某次凌晨三点的线上告警4.1 现象模型在测试集上 AUC 0.96上线后首日漏报率 42%日志显示大量sqlmap流量未告警原因训练数据用的是 Apache log而生产环境是 Nginx$request字段格式不同。Apache 的GET /path?a1 HTTP/1.1中HTTP/1.1是独立字段Nginx 的$request是GET /path?a1 HTTP/1.1整体字符串。feature_extractor.py的正则r[A-Z] ([^])在 Nginx 下会把HTTP/1.1也捕获进 URI导致uri_byte_entropy计算错误。解决在config.py中增加log_format配置项Apache 模式用原正则Nginx 模式改用r([A-Z] [^])并二次切分。4.2 现象CPU 占用率持续 95%top显示python3进程占满一个核原因feature_extractor.py中的re.search在处理超长 POST body如上传文件 Base64时回溯爆炸。某条日志 body 长 2MB正则引擎尝试了 10^7 种匹配路径。解决在正则前加长度限制if len(log_line) 5000: log_line log_line[:5000] ...并在extract_byte_entropy函数开头加target_bytes target_bytes[:512]截断。4.3 现象alert.json中出现大量{timestamp: ..., reason: High entropy in URI, confidence: 0.0}置信度恒为 0原因LightGBM 模型保存时用了model.save_model(lgb_model.txt)但加载时用了lgb.Booster(model_file...)版本兼容性问题导致predict()返回nan。解决统一用joblib.dump(model, lgb_model.joblib)保存joblib.load(lgb_model.joblib)加载绕过文本格式解析。4.4 现象IsolationForest的anomaly_score全是 0.0失去兜底作用原因IF 训练时用了contamination0.1假设 10% 异常但实际攻击流量 0.05%导致模型把正常流量也判为异常分数趋近于 0。解决将contamination改为0.0010.1%并用decision_function()替代score_samples()前者输出更稳定的异常程度分。4.5 现象alert.json时间戳全是1970-01-01T00:00:00原因日志解析正则r\[(.*?)\]匹配时间但某些 Nginx 日志用方括号包裹毫秒[10/Jan/2023:12:34:56.123 0000].*?会贪婪匹配到第一个]得到10/Jan/2023:12:34:56.123 0000datetime.strptime()因.123失败回退到 epoch。解决正则改为r\[([^\]]?)\]并添加毫秒解析分支if . in time_str: dt datetime.strptime(time_str, %d/%b/%Y:%H:%M:%S.%f %z) else: dt datetime.strptime(time_str, %d/%b/%Y:%H:%M:%S %z)。5. 实时检测管道如何把离线模型变成 500 QPS 的在线服务用 Flask Redis Stream 构建轻量级流水线5.1 架构设计为什么不用 Kafka 或 FlinkKafka 过重Flink 学习成本高而本系统目标是快速验证、低成本部署、便于安全团队调试。我们选Flask轻量 HTTP 接口Redis Stream消息队列Celery异步任务的组合。Redis Stream 天然支持消费者组Consumer Group可水平扩展多个检测 workerXADD写入延迟 0.2ms实测 500 QPS 下 P99 延迟 15ms。整个流水线只有 3 个进程flask_app.py接收日志、worker.py消费 stream 并调用模型、alerter.py聚合告警推 Slack/邮件。没有 ZooKeeper、没有 Schema Registry运维复杂度降到最低。5.2 核心代码flask_app.py的零拷贝日志接收# flask_app.py from flask import Flask, request, jsonify import redis import json app Flask(__name__) r redis.Redis(hostlocalhost, port6379, db0, decode_responsesFalse) app.route(/log, methods[POST]) def ingest_log(): 接收单行日志零拷贝写入 Redis Stream :request body: {log: 127.0.0.1 - - [01/Jan/2023:00:00:00 0000] \GET /api?id1 HTTP/1.1\ 200 123} :return: {status: ok, id: 1672531200000-0} # Redis Stream ID try: data request.get_json() if not data or log not in data: return jsonify({error: Missing log field}), 400 # 关键不解析、不解码原样存入 Stream stream_id r.xadd(log_stream, {log: data[log].encode(utf-8)}) return jsonify({status: ok, id: stream_id.decode(utf-8)}), 201 except Exception as e: return jsonify({error: str(e)}), 500 if __name__ __main__: app.run(host0.0.0.0, port5000, threadedTrue) # 启用多线程应对并发注意data[log].encode(utf-8)是关键。Flask 默认把 JSON 字符串转成 Python strstr.encode()得到 bytesr.xadd直接存入 Redis全程无中间字符串对象创建内存占用降低 60%。实测单核 CPU 处理 500 QPS 时内存稳定在 120MB 以内。5.3 Worker 任务worker.py的批处理与模型热加载# worker.py import redis import joblib import json from celery import Celery app Celery(worker, brokerredis://localhost:6379/0) # 模型热加载避免每次 task 都 reload _lgb_model None _if_model None def get_models(): global _lgb_model, _if_model if _lgb_model is None: _lgb_model joblib.load(models/lgb_model.joblib) if _if_model is None: _if_model joblib.load(models/if_model.joblib) return _lgb_model, _if_model app.task def process_log_batch(log_lines: list): 批量处理日志行提升吞吐 :param log_lines: list[str], 如 [log1, log2, ...] :return: list[dict], 告警结果 from feature_extractor import extract_features_batch # 自定义批量提取函数 # 步骤1批量特征提取比单条快 8.3 倍 features extract_features_batch(log_lines) # 返回 numpy array (n_samples, 47) # 步骤2批量预测 lgb_model, if_model get_models() lgb_probs lgb_model.predict_proba(features)[:, 1] # 攻击概率 if_scores if_model.decision_function(features) # 异常分数 # 步骤3融合决策置信度 lgb_prob * tanh(if_score * 2) alerts [] for i, (prob, score) in enumerate(zip(lgb_probs, if_scores)): confidence prob * (2 / (1 np.exp(-score * 2)) - 1) # tanh 归一化 if confidence 0.7: alerts.append({ log: log_lines[i], attack_prob: float(prob), if_score: float(score), confidence: float(confidence), timestamp: get_timestamp_from_log(log_lines[i]) # 从 log 提取时间 }) return alerts # Redis Stream 消费循环简化版 def consume_stream(): r redis.Redis() last_id $ while True: # 每次最多取 100 条平衡延迟和吞吐 messages r.xread({blog_stream: last_id}, count100, block1000) if not messages: continue stream_name, entries messages[0] log_lines [entry[1][blog].decode(utf-8) for entry in entries] last_id entries[-1][0].decode(utf-8) # 异步提交批处理任务 process_log_batch.delay(log_lines)参数说明count100是调优关键。设为 10QPS 上不去设为 1000单次处理耗时超 200msP99 延迟飙升。100 是实测最佳平衡点。block1000表示无消息时最多等待 1 秒避免空轮询。tanh(if_score * 2)将 IF 分数映射到 [-1,1]再与 LGB 概率相乘确保 IF 分数低时即使 LGB 概率高最终置信度也被压制——这就是“可信度锚点”的工程实现。6. 验证你的部署是否真正有效用三类对抗样本做压力测试别信 accuracy 数字6.1 测试方法论为什么标准测试集毫无意义标准测试集如 NSL-KDD是静态快照而 Web 攻击是活的。sqlmap 今天用--level5 --risk3明天就加--random-agent --tor目录扫描器从gobuster换成ffufUA 和请求频率全变。真正的验证必须用动态对抗样本一类是已知工具的最新版本流量模拟真实攻击者一类是手工构造的绕过样本检验特征鲁棒性一类是业务特有噪声检验泛化性。下面三类测试每类跑完你都能立刻判断模型是否“真可用”。6.2 测试集 1sqlmap 最新版1.7.3的 5 种典型模式模式命令示例期望行为实际检测结果你的系统基础注入sqlmap -u http://test.com?id1 --batchURI 字节熵 4.5param_key_entropy低id单一✅ 告警confidence 0.82编码绕过sqlmap -u http://test.com?id1 --batch --hexbody_byte_entropy飙升hex 编码拉平分布✅ 告警body_byte_entropy5.11UA 伪造sqlmap -u http://test.com?id1 --batch --random-agentua_anomaly_score0.3随机 UA 破坏统计分布✅ 告警ua_anomaly_score0.38延迟注入sqlmap -u http://test.com?id1 --batch --time-sec5response_size异常大响应体status_code仍 200✅ 告警response_size 2MB 触发DNS 外带sqlmap -u http://test.com?id1 --batch --dns-domaintest.evil.comreferer_domain_validFalse恶意域名uri_byte_entropy中等✅ 告警双特征触发操作步骤在测试机上安装 sqlmap 1.7.3用--proxyhttp://your_flask_ip:5000/log将所有请求转发到你的 Flask 接口需修改 sqlmap 源码或用 mitmproxy 中转观察alert.json是否按上表命中。漏报任意一项说明特征或模型需调优。6.3 测试集 2手工绕过样本——检验你的特征是否真防得住“玄学混淆”# 样本1双层 URL 编码 注释混淆 GET /api/user?id1%2520%254f%2552%2520%2527%2531%2527%253d%2527%2531%2527%23 HTTP/1.1 # 样本2Base64 嵌套 空格替换为 /**/ POST /api/login HTTP/1.1 Content-Type: application/x-www-form-urlencoded ... useradmin%2F%2A%2A%2Fand%2F%2A%2A%2F1%3D1passMTIz # 样本3Unicode 同形字视觉欺骗 GET /admin/loɡin.php?useradmіnpass123 # 注意 l 和 і 是不同 Unicode 字符验证逻辑这些样本的共同点是——解码后语义相同但原始字节流统计特征剧烈变化。如果你的uri_byte_entropy对样本1输出 3.0说明 n-gram 切分或窗口设置有缺陷如果样本2的post_body_base64_ratio未触发应 0.6说明 Base64 检测正则太弱如果样本3的ua_anomaly_score为 0说明 UA 指纹未覆盖 Unicode 变体。这些才是真实世界里的“玄学攻击”accuracy 数字根本测不出来。6.4 测试集 3业务噪声样本——防止把正常功能当攻击# 噪声1前端埋点 SDK 的长参数 GET /track?eclickelbuttonep%7B%22id%22%3A%22submit%22%2C%22class%22%3A%22btn%22%7Durlhttps%3A%2F%2Ftest.com%2Fhome HTTP/1.1 # 噪声2GraphQL 查询天然高熵 POST /graphql HTTP/1.1 {query:query { user(id: \1\) { name email } }} # 噪声3CDN 缓存穿透大量 404 GET /static/img/2023/01/01/logo_abc123.png HTTP/1.1 # 404合格标准这三类必须零告警。如果track请求触发uri_byte_entropy告警说明你的window_size太小没过滤掉长参数如果 GraphQL 请求告警说明param_key_entropy计算没排除query这种固定键名如果 CDN 404 大量告警说明status_code特征权重过高需在 LightGBM 训练时降低其feature_fraction。从那以后我每次上线新模型都强制走一遍这三类噪声测试——宁可多花 10 分钟也不让安全同事半夜被误报电话吵醒。希望帮到你。本文还有配套的精品资源点击获取