简介:这是一份面向网络安全研究人员、网络运维人员及高校相关专业师生的学术参考文献,PDF格式全文共1个文件,压缩包大小4.46MB。资料提出基于多源异构数据融合的网络安全态势评估体系,针对单点网络数据难以准确检测恶意活动和评估网络状况的问题,构建了流量探测、属性提炼、决策引擎、多源融合、态势评估五大模块。文中以BP神经网络作为决策引擎分析各数据源,采用指数加权D-S证据理论融合多源输出,并结合层次化网络威胁评估方法量化威胁状况;实验显示多源融合可将攻击识别准确率提升至88.7%。已有267人学习下载,适合需要了解态势评估建模思路、数据融合算法应用及实验验证方法的读者作为参考。
1. 多源异构数据融合:为什么单点探测器一定不够
做网络安全的人大概都有过这种体验:一台 Snort 在机房里跑得热火朝天,报警刷了几万条,你根本分不清哪些是真攻击、哪些是误报;换个探测器看同一个流量,结论可能完全相反。这不是工具不行,而是单点检测的天然盲区——每个探测器只能看到网络的一个切片,单独看都有道理,合起来却凑不出完整画面。这篇《基于多源异构数据融合的网络安全态势评估体系》解决的就是这个问题:用 Netflow、Snort、Suricata 三类异构数据源并行采集,经 BP 神经网络分别做决策,再用带指数权重的 D-S 证据理论把各路结果融合,最终把攻击识别的准确率从单源的 85.2% 提到 88.7%,然后按服务层、主机层、网络层三级结构给出态势曲线。适合正在做安全数据分析、态势感知平台,或者想把手头 IDS 报警数据盘活成可量化的态势指标的从业者。
2. 从流量到属性:三类探测器的差异化特征工程
2.1 为什么不能直接拿原始字段训练
论文的流量探测模块部署了三类探测器:Netflow 负责网络层流量统计,Snort 和 Suricata 做入侵检测报警。三者本质是不同类型的传感器——Netflow 给你的是连接记录(时间、协议、IP、端口、包数、字节数),Snort 给你的是规则匹配出的报警事件,Suricata 则是流量元数据加报警的混合体。
如果用原始字段直接喂给分类器,效果会很差。论文给了很直白的原因:地址、端口、应用服务、时间这类离散属性对攻击类型几乎没有区分度。比如源 IP 的绝对值在训练集和测试集里分布不同,模型学到的是"这个 IP 是攻击"而不是"这种流量模式是攻击"。所以它们设计了专门的统计算法,把四种原始属性揉成网络连接属性。核心思想是:不关心 IP 是谁,关心在时间窗口内源 IP 与目的 IP 之间出现了多少次连接、端口分布是否异常。
需要注意的是,原始论文里三个探测器的基础属性设计不同:Netflow 有 13 个字段偏流量统计,Snort 有报警数量/报警类型,Suricata 两者兼顾。融合的前提是各数据源属性不完全重叠,如果三个源长得一模一样,融合就没有增量信息了。
2.2 网络连接属性的统计构造
表 4 里的 8 个统计属性是整篇论文特征工程的核心,我把它们合并整理一下:
| 属性名 | 计算方式 | 作用 |
|---|---|---|
| is_sm_ips_prots | Sip 与 Dip 相同且 Sport 与 Dport 相同则置 1 | 识别同一对主机的重复连接 |
| ct_dst_ltm | 每 100 条记录中 Ltime、Dip 相同的数量 | 目的 IP 热度 |
| ct_src_ltm | 每 100 条记录中 Ltime、Sip 相同的数量 | 源 IP 热度 |
| ct_src_dport_ltm | 每 100 条记录中 Ltime、Sip、Dport 相同的数量 | 源 IP 对目的端口的热度 |
| ct_dst_sport_ltm | 每 100 条记录中 Ltime、Dip、Sport 相同的数量 | 目的 IP 对源端口的热度 |
| ct_dst_src_ltm | 每 100 条记录中 Ltime、Sip、Dip 相同的数量 | 源到目的连接热度 |
| ct_srv_src | 每 100 条记录中 Sip、Serve 相同的数量 | 源 IP 服务热度的聚合 |
| ct_srv_dst | 每 100 条记录中 Dip、Serve 相同的数量 | 目的 IP 服务热度 |
用 Python 构造这几个特征的做法大致如下:
import pandas as pd def build_connection_features(df, window=100): """ 论文表4 网络连接属性构造,基于时间排序后的流记录 df 需包含: Sip, Dip, Sport, Dport, Ltime, Serve 返回带 8 个统计特征的副本 """ df = df.sort_values("Ltime").reset_index(drop=True) # 1) 源目IP与源目端口是否相同 df["is_sm_ips_prots"] = ( (df["Sip"] == df["Dip"]) & (df["Sport"] == df["Dport"]) ).astype(int) # 2) 滑动窗口内统计,窗口大小=100条记录 df["ct_dst_ltm"] = df["Dip"].rolling(window, min_periods=1).apply( lambda x: x.value_counts().iloc[0], raw=False ) df["ct_src_ltm"] = df["Sip"].rolling(window, min_periods=1).apply( lambda x: x.value_counts().iloc[0], raw=False ) df["ct_src_dport_ltm"] = df.apply( lambda r: int(((df["Sip"] == r["Sip"]) & (df["Dport"] == r["Dport"])).sum()), axis=1 ) # 其余 ct_ 特征同理,按窗口内组合键计数 return df这段代码的要点在窗口设计。论文里用的是"每 100 条记录"一个窗口,不是按时间窗口切分,目的是让统计量跟随流量密度自适应——流量大时窗口时间短,流量小时窗口时间长。rolling(window, min_periods=1)保证前几条数据也有值,避免冷启动。apply里取窗口内出现次数最多的值作为热度,比固定阈值灵活。
实际复现时我会用groupby代替apply,性能会快很多,但语义等价。另一个通用做法是把 100 条记录窗口换成 5 分钟时间窗口——论文后面做态势评估时用的是 5 分钟窗口,但特征层面窗口按记录数走,两套窗口各管各的,别混用。
2.3 数值归一化与非数值向量化
为什么必须归一化?看 Netflow 原始字段就明白了:Bps(比特数每秒)动辄上万,Sport(源端口)只有 0~65535,如果直接进 BP 神经网络,Bps 的梯度会完全压过端口属性,数值变化小的属性等于被废掉。论文原文说得直白:"避免数值变化较大的属性覆盖数值较小的属性"。
from sklearn.preprocessing import MinMaxScaler, LabelEncoder # 数值型归一化到 [0, 1],消除量纲差异 num_cols = ["Pkt", "Byt", "Bps", "Pps", "Bpp", "Ip_len"] scaler = MinMaxScaler() df[num_cols] = scaler.fit_transform(df[num_cols]) # 非数值型向量化: 协议、服务、告警类型转为整数编码 for col in ["Protocol", "Serve", "Attack_type"]: df[col + "_enc"] = LabelEncoder().fit_transform(df[col].astype(str)) # 攻击类型作为标签也做编码 label_enc = LabelEncoder() df["y"] = label_enc.fit_transform(df["Attack_type"])这里要注意LabelEncoder有个坑:它输出的整数带了大小关系,比如tcp=0, udp=1会被模型误认为 udp 比 tcp "大"。严格做法是用OneHotEncoder或pd.get_dummies。但论文里 BP 神经网络的输入层能接受编码向量,所以实际操作中,如果类别基数小(协议就几种、告警类型几十种),one-hot 更安全;如果类别基数大,可以先 embedding 再进网络,但那属于后话。
属性提炼模块的产出就是一张规范化后的特征表:数值列全部在 [0,1] 区间,非数值列变成稠密编码。这张表才是 BP 神经网络的真正输入。
3. BP 决策引擎:三层网络结构与三类数据源的训练差异
3.1 为什么选 BP 而不是别的分类器
决策引擎模块的任务是把"核心属性"映射到"攻击类型"。论文引用了 Hecht Nielson 的结论:只含一个隐藏层的网络可以逼近闭区间上的任一连续函数。这说明 BP 在理论上能拟合任意复杂的属性到攻击类型的映射关系,不需要预先假设数据分布。
论文实验里用了 2 层隐藏层、每层 32 个神经元。为什么不用 1 层?单隐藏层能逼近连续函数,但要达到同样精度往往需要更多神经元;两层结构在参数量适中的前提下对非线性特征的表达能力更强,训练也相对稳定。在 UNSW-NB15 这个数据集上,2×32 的配置是够用的——如果你复现时发现欠拟合,先加神经元数而不是急着加深网络。
3.2 前向传播与反向传播的数学链条
网络结构定义:输入层 m 个神经元,隐藏层 h 个,输出层 n 个。隐藏层第 j 个节点的输出是
d_j = f( Σ_m x_i * w_ji )输出层第 k 个节点的输出是
ŷ_k = f( Σ_h d_j * v_kj )均方误差是
E = 0.5 * Σ_n (ŷ_k - y_k)^2权值更新走标准梯度下降:输出层权值 Δv_kj 由误差对输出的偏导链式乘出,隐藏层权值 Δw_ji 还要再把误差从输出层往回调一层。学习率 η 控制每一步的步长,论文公式里没有给出具体取值,常见的做法是把 η 设在 0.01~0.1 区间,配合自适应优化器使用。
3.3 一个最小可跑的 BP 训练骨架
用 PyTorch 复现论文的决策引擎,结构大致如下:
import torch import torch.nn as nn class DecisionEngine(nn.Module): def __init__(self, in_dim, hidden=32, out_dim=10): super().__init__() self.net = nn.Sequential( nn.Linear(in_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), # 论文用2层隐藏层 nn.ReLU(), nn.Linear(hidden, out_dim), nn.Softmax(dim=1) # 输出为各类攻击概率 ) def forward(self, x): return self.net(x) # 每个数据源分别训练一个决策引擎 model_netflow = DecisionEngine(in_dim=netflow_train.shape[1]) model_snort = DecisionEngine(in_dim=snort_train.shape[1]) model_suricata = DecisionEngine(in_dim=suricata_train.shape[1]) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model_netflow.parameters(), lr=0.005) for epoch in range(50): optimizer.zero_grad() out = model_netflow(x_train) loss = criterion(out, y_train) loss.backward() optimizer.step()代码里最关键的设计是三个数据源各自训练一个独立模型。不要把所有数据拼在一起喂给同一个网络——论文第三部分实验明确指出,不同探测器对识别不同类型攻击的优势不同(Netflow 擅长 Analysis 和 Normal,Snort 擅长 Backdoor 和 Worm,Suricata 擅长 Dos),这是多源融合的立论基础。拼在一起训练会把各源的优势互相稀释,融合时反而没有差异化信息可用。
输出层用Softmax(dim=1)是因为后面 D-S 融合需要"攻击类型概率"作为证据输入。如果只做单模型分类,用CrossEntropyLoss配合不带 softmax 的 logits 就行;但这里 softmax 的概率值就是 D-S 理论里的基本概率分配,少了这一步,融合模块接不上。
3.4 单源性能的边界在哪里
论文表 8 给出了三个单源的基准:Netflow 准确率 85.2%、Snort 80.2%、Suricata 79.5%,误警率分别为 19.9%、26.1%、25.6%。注意 Netflow 虽然准确率最高,误警率也最低,但它对某些攻击类型是盲的;Snort 准确率低,但对 Backdoor、Worm 的识别能力是三个源里最强的。
这说明评估一个决策引擎好坏不能只看总准确率。我复现这类体系时的习惯是:先给每个数据源单独算混淆矩阵,确认每个源在哪些攻击类别上"有优势",再进入融合阶段。如果某个源所有类别都表现平庸,那它大概率是特征工程出了问题,而不是融合能救的。
4. 复现避坑指南:训练阶段最容易翻车的五个位置
4.1 类别不平衡导致模型"假装工作"
现象:训练出的 BP 在测试集上准确率有 80% 多,但看混淆矩阵发现它把大部分攻击都判成了 Normal,靠猜多数类刷分。
原因:UNSW-NB15 的 5% 子集中 Normal 流量占比很高,BP 用交叉熵损失训练时,多数类的梯度主导了参数更新,模型学到的实际上是"全预测 Normal 损失也不大"。这正是论文里误警率一直降不下来的根因之一——不仅仅是融合算法的问题,训练阶段就偏了。
解决:对少数类做加权采样,或给损失函数加类别权重。CrossEntropyLoss(weight=torch.tensor([...]))即可实现。另外建议训练集做分层抽样,保证每个攻击类别在训练和验证集里的比例一致。
4.2 非数值属性直接进网络
现象:模型训练 loss 不下降,或者收敛后精度远低于论文的 85%。
原因:把 IP 地址、协议名、告警类型这种字符串属性直接塞进了nn.Linear,PyTorch 根本不会帮你做编码,模型把一个字符串当成了一个"浮点数特征",梯度毫无意义。
解决:严格按 2.3 节的流程走——数值列归一化、非数值列 one-hot 或 label 编码。这里有个实操细节:告警类型这种高基数类别,one-hot 会让输入维度爆炸,可以先用CountVectorizer按出现频率筛出 top-K 类别,其余归入 unknown 类。
4.3 滑动窗口统计泄漏未来信息
现象:特征工程的准确率在训练集上极高,测试集上骤降。
原因:rolling窗口和apply的组合如果没有sort_values("Ltime")排序,窗口内会混进乱序记录;更隐蔽的问题是如果窗口按整份文件滚动而不是按主机分组滚动,同一主机的连接会被其他主机的记录打断,统计属性失去语义。
解决:构造特征前务必按时间戳排序,按 Sip 或 Dip 分组后再单独滚动。论文的 100 条窗口是全局窗口,实际操作中我一般会先按主机分组再做窗口统计,这样"ct_src_ltm"才能真实反映单个主机的连接热度。
4.4 误警率偏高是数据集的固有属性,不要死磕
现象:融合后误警率 13.7%,你反复调阈值想降到 10% 以下,结果准确率也跟着掉。
原因:论文表 9 引用了其他团队在同一数据集上的结果——决策树 15.8%、逻辑回归 18.5%、朴素贝叶斯 18.6%、ANN 21.1%,全部高于 15%。UNSW-NB15 里部分攻击流量和正常流量在特征空间高度重叠,这个误警率下界是由数据集本身决定的。
解决:接受 13%~15% 的误警率作为这个数据集的合理区间。评估体系是否有效,重点看融合相对单源是否改善了误警率(19.9%→13.7%),而不是追求绝对数值。我在复现时还会额外看每个攻击类别的召回率,特别是 Dos、Exploit 这类高危攻击,漏报比误报更危险。
4.5 三个数据源的时间对齐不一致
现象:Netflow 的窗口是 5 分钟聚合,Suricata 的报警是秒级事件,融合时两者对不上,同一时间窗口内 Netflow 说没有攻击、Suricata 报了 50 条告警。
原因:流量探测模块的三种探测器采集粒度天然不同,Netflow 偏统计、IDS 偏事件,不做时间对齐就直接融合等于把不同时间尺度的信息强行叠加。
解决:统一先按 5 分钟时间窗口(论文实验就是 33000 秒切成 110 个窗口)聚合所有源的属性。Netflow 统计特征直接落在窗口内,Snort/Suricata 报警按窗口内条数聚合成报警数量、报警类别分布。代码上就是df.groupby(pd.Grouper(key="Ltime", freq="5min"))之后分别聚合,再把三个源按窗口笛卡尔对齐。
5. 指数加权 D-S 融合:PSO 如何把准确率推到 88.7%
5.1 经典 D-S 证据理论的困境
多源融合模块要做的事是:三个 BP 决策引擎各自输出一个"攻击类型概率向量",把它们合成一个更可信的结果。D-S 证据理论天然适合这个场景,但经典 D-S 合成规则有一个著名的问题——当两个证据源对同一命题给出冲突意见时,合成结果可能违背直觉。
论文采用的是指数加权变体:每个证据源的第 i 类攻击概率 m_j(Ai) 被提升到指数幂 w_ji 之后再做乘积。权重 w_ji 不是拍脑袋定的,而是用粒子群优化算法搜出来的。这个设计的意图是:某个数据源在某一类攻击上更有优势,就给它更高的指数权重,让它在融合结果里说话更响亮。
5.2 指数加权公式与归一化
融合后的第 i 类攻击概率是
m(Ai) = Π_j m_j(Ai)^w_ji / KK 是归一化常数:
K = Σ_i Π_j m_j(Ai)^w_ji这里 g 是数据源数量(本实验为 3),n 是攻击类型数(UNSW-NB15 取 10 类,含 Normal)。指数权重 w_ji 是优化变量,不是先验给定的——PSO 在 [0,1] 范围内搜索每个数据源对每类攻击的权重,目标是让融合概率和真实标签的均方误差最小:
argmin Σ_i (m(Ai) - y_i)^25.3 粒子群优化器的实现
import numpy as np def pso_fusion_weights(evidence, y_true, swarm_size=100, dim=None, epochs=60): """ evidence: shape [n_samples, n_sources, n_classes] 三个BP引擎的softmax输出 y_true: one-hot 真实标签 返回: 每个源每类攻击的指数权重矩阵 [n_sources, n_classes] """ n_sources, n_classes = evidence.shape[1], evidence.shape[2] dim = n_sources * n_classes # 初始化粒子位置 ∈ [0,1],速度随机 x = np.random.rand(swarm_size, dim) v = np.random.rand(swarm_size, dim) * 0.1 p_best = x.copy() g_best = x[0].copy() def fusion_loss(pos): w = pos.reshape(n_sources, n_classes) # 指数加权乘积: m(Ai) = Π m_j(Ai)^w_ji / K logits = np.sum(w[np.newaxis, :, :] * np.log(evidence + 1e-12), axis=1) m = np.exp(logits) m /= m.sum(axis=1, keepdims=True) return np.mean((m - y_true) ** 2) for _ in range(epochs): for i in range(swarm_size): loss = fusion_loss(x[i]) if loss < fusion_loss(p_best[i]): p_best[i] = x[i].copy() g_best = p_best[np.argmin([fusion_loss(p) for p in p_best])] # 速度更新: v = c3*v + c1*r1*(p_best - x) + c2*r2*(g_best - x) c1, c2, c3 = 1.5, 1.5, 0.5 r1, r2 = np.random.rand(dim), np.random.rand(dim) v = c3 * v + c1 * r1 * (p_best - x) + c2 * r2 * (g_best - x) x = np.clip(x + v, 0, 1) return g_best.reshape(n_sources, n_classes)几个值得说清楚的参数细节。PSO 的群体规模论文给了 100,搜索空间限定在 [0,1] 是因为指数权重必须非负,超过 1 的权重会放大证据差异,容易让融合结果被单一源主导。速度更新里的 c1、c2 是"推向局部最优/全局最优"的权重,c3 是惯性权重,常规取值是 c1=c2=1.5、c3=0.5,论文公式里也是这个结构。代码里np.log(evidence + 1e-12)是为了防止 evidence 里出现 0 导致 log 崩掉——softmax 输出理论上不会严格为 0,但浮点精度下可能下溢,加一个极小量是稳妥做法。
融合后的效果,论文表 8 写得很清楚:
| 指标 | Netflow | Snort | Suricata | D-S | PSO-DS |
|---|---|---|---|---|---|
| 准确率 | 85.2 | 80.2 | 79.5 | 87.8 | 88.7 |
| 误警率 | 19.9 | 26.1 | 25.6 | 14.9 | 13.7 |
注意两个增量:经典 D-S 已经把准确率提到 87.8%,说明融合机制本身就有效;PSO 只把准确率再推高了 0.9 个百分点,但把误警率压到了 13.7%。这说明指数权重的价值主要在抑制误报——让优势数据源在对应攻击类别上权重更大,从而减少弱势数据源的"胡说八道"。这也是为什么我会强调先分析单源混淆矩阵:你只有知道每个源在哪类攻击上强,回头看 PSO 搜出来的权重矩阵,才能验证结果是否合理。
融合阶段常见的一个误用是直接对三个源的 softmax 输出做平均。平均是最粗暴的融合,牺牲了各源的差异化优势。另一个误用是融合后不再归一化直接拿去和阈值比,这类错误会导致态势评估模块的服务层态势值虚高。
6. 层次化态势评估:从威胁量化到三级曲线下钻
6.1 威胁等级划分与权系数量化
态势评估模块要回答的问题是:融合出"某主机正在遭受 3 类攻击,概率分别是 0.6/0.3/0.1"之后,这个信息怎么变成一个可以用来决策的数值?
论文的路径分两步:先按 Snort 的报警规则把攻击分成高中低三档,再用权系数理论算出每档对应的威胁因子值。威胁等级划分原则可以压缩成一张表:
| 攻击目标 | 具体行为 | 威胁等级 |
|---|---|---|
| 计算机权限 | 获取管理员/普通用户控制权限、执行代码 | 高 |
| 隐私信息 | 获取系统内部信息 | 中 |
| 带宽消耗 | 拒绝服务、消耗带宽 | 中 |
| 网络扫描 | 扫描获取网络信息 | 低 |
威胁因子值不是手动指定的,而是用一个离散化的权系数函数算出来的。n 为攻击等级个数,i 为排列顺序,当 i 位于序列前一半时威胁因子大于 0.5,后一半小于 0.5,中间档恰好等于 0.5。论文实验里的取值是:高威胁 0.726、中威胁 0.611、低威胁 0.389,Normal 为 0。这个量化的好处是给了"严重程度"一个连续数值,后续逐层加权才有运算基础。
6.2 三层态势的递进计算
三层评估是自底向上的。服务层某台主机上第 j 个服务的态势值是时间窗口内所有攻击的概率乘以威胁因子再累加:
s_kj(t) = Σ m(attack_i) * 10^f(attack_i)注意这里有个放大系数 10。为什么是 10?论文没有解释,但这种指数放大在安全场景里有实际意义——高危攻击哪怕概率低,贡献的态势值也不能被中低危攻击的数量淹没。如果只有 2 个高危攻击各 0.3 概率,线性累加只有 0.6,很容易被 20 个低危攻击(各 0.1)的 2.0 压过,这是不合理的;指数放大让高危攻击的少量出现就能在曲线上形成尖峰,便于管理员感知。
主机层态势是服务层态势向量与本机服务权值向量的加权和,服务权值由使用该服务的用户数和频率归一化得到;网络层态势是各主机态势向量与主机权值向量的加权和,主机权值同理。这三级关系用一段短代码可以表达:
def eval_situation(svc_probs, threat_factor, service_weights, host_weights): # svc_probs: [host, service, attack_type] 融合概率 # 服务层: 概率 * 10^威胁因子 累加 svc_situation = (svc_probs * (10 ** threat_factor)).sum(axis=2) # 主机层: 服务态势 * 服务权值 加权求和 host_situation = (svc_situation * service_weights).sum(axis=1) # 网络层: 主机态势 * 主机权值 加权求和 net_situation = (host_situation * host_weights).sum() return svc_situation, host_situation, net_situation这段代码对应论文公式 (12)(13)(15)。三个参数的形状得事先对齐:svc_probs是三维张量,threat_factor是攻击类别维度的向量,service_weights和host_weights分别是服务和主机的权值向量,全部要在时间窗口内按 5 分钟聚合。
6.3 验证技巧:从网络层曲线反向定位问题主机
论文实验部分展示了三个层面的态势曲线:服务层能看出某主机上 HTTP 服务一天内遭受 2 次强烈攻击;主机层能对比三台主机的受攻击程度;网络层能看到全天 4 次较大波动。
我的复现习惯是反向验证:先看网络层曲线找到波动的时间窗口,再下钻到主机层看哪台主机的态势值在那个窗口内抬升,最后落到服务层确认是 DNS、HTTP 还是 SMTP 出了问题。这样做的好处是过滤掉了大量不重要的低危攻击——网络层曲线能自动把"大量低危攻击累积"和"少量高危攻击"区分开,因为高危攻击有 10 的指数放大。当初我按这个流程复现时,最大的感受是:单源时代看报警列表看到眼花,三层体系下只需要盯住曲线突变点,再下钻定位,效率提升是肉眼可见的。
等到我自己搭这套体系时,会在融合概率输出后额外加一个"高危攻击专属通道"——把威胁因子大于 0.7 的攻击单独列一条实时告警,不走态势曲线,因为态势评估本质是滞后几分钟的统计,而高危攻击需要即时响应。从那以后我每次做态势评估系统的验证,都强制走一遍"网络层找波动 → 主机层定位 → 服务层确认 → 高危攻击单独排雷"的完整循环,这个习惯帮我避开了好几回漏报。希望帮到你。
本文还有配套的精品资源,点击获取