
零日攻擊是最嚴重的網路安全威脅之一。它們利用先前未知的漏洞,從而繞過現有的入侵檢測系統 (IDS)。傳統的基於特徵的入侵檢測系統 (IDS) 在這方面失效了,因為它依賴於已知的攻擊模式。為了檢測此類攻擊,模型需要學習正常的網路行為,並在其偏離正常行為時自動標記。
一個很有前景的解決方案是應用去噪自編碼器 (DAE),這是一種無監督深度學習模型,旨在學習正常流量的魯棒表示。其主要思想是透過在訓練過程中稍微破壞輸入,DAE 學會重建原始的、乾淨的資料版本。這迫使模型捕捉資料的本質表示,而不是記住噪聲。當面對未知的零日攻擊時,
損失函式(即重建誤差峰值)會進行異常檢測。在本文中,我們將瞭解如何在 UNSW-NB15 資料集上使用 DAE 進行零日攻擊檢測。
去噪自編碼器:核心思想
在去噪自編碼器中,我們會在輸入傳遞給編碼器之前,故意新增噪聲。然後,網路會學習重建原始的、乾淨的輸入。為了使模型專注於有意義的特徵而非細節,我們會使用隨機噪聲來破壞輸入資料。我們將其數學表達為:

重建損失也稱為損失函式,它評估原始輸入資料 x 與重建輸出資料 x̂ 之間的差異。較低的重建誤差表明模型忽略了噪聲並保留了輸入的基本特徵。下圖顯示了去噪自動編碼器的示意圖。

去噪自編碼器
示例:二進位制輸入案例
考慮二進位制輸入 (x ∈ {0,1}。我們以機率 q 翻轉某個位或將其設定為 0;否則,我們保持不變。如果我們允許模型最小化關於損壞輸入 x 的誤差,它只會學習複製損壞部分。但是,由於我們強制它重建真實的 x,它必須從特徵之間的關係中推斷缺失的資訊。這使得 DAE 模型能夠超越記憶,學習關於輸入的更深層結構。在測試過程中,它能夠提高泛化能力。在網路安全領域,去噪自編碼器能夠檢測偏離正常模式的未知攻擊或零日攻擊。
案例研究:使用去噪自編碼器進行零日攻擊檢測
此示例說明了去噪自編碼器如何在 UNSW-NB15 資料集中檢測零日攻擊。我們訓練模型來學習正常流量的底層結構,而不會讓異常資料會影響模型。在推理過程中,模型會評估與正常模式明顯偏離的網路流量,例如與零日攻擊相關的流量,這些流量會導致較高的重構誤差,從而實現異常檢測。
步驟 1. 資料集概覽
UNSW-NB15 資料集是一個基準資料集,用於評估入侵檢測系統的效能。它包含正常樣本和九個攻擊類別,包括模糊器、Shellcode 和漏洞利用程式。為了模擬零日攻擊,我們僅對正常流量進行訓練,並保留 Shellcode 攻擊進行測試。這確保了模型能夠基於之前未見過的攻擊行為進行評估。
步驟 2. 匯入庫並載入資料集
我們匯入必要的庫並載入 UNSW-NB15 資料集。然後,我們進行數值預處理,分離標籤和分類特徵,並僅關注正常流量進行訓練。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.metrics import roc_curve, auc
import tensorflow as tf
from tensorflow. keras import layers, Model
from tensorflow. keras.callbacks import EarlyStopping
# Load UNSW-NB15 dataset
df = pd. read_csv("UNSW_NB15.csv")
print ("Dataset shape:", df. shape)
print (df [['label’, ‘attack cat']].head())
輸出:
Dataset shape: (254004, 43)First five rows of ['label','attack_cat']: label attack_cat0 0 Normal1 0 Normal2 0 Normal3 0 Normal4 1 Shellcode
輸出顯示資料集有 254,004 行和 43 列。標籤 0 表示正常流量,1 表示攻擊流量。第五行是 Shellcode 攻擊,我們用它來檢測零日攻擊。
步驟 3. 預處理資料
# Define target
y = df['label']
X = df.drop(columns=['label'])
# Normal traffic for training
normal_data = X[y == 0]
# Zero-day traffic (Shellcode) for testing
zero_day_data = df[df['attack_cat'] == 'Shellcode'].drop(columns=['label','attack_cat'])
# Identify numeric and categorical features
numeric_features = normal_data.select_dtypes(include=['int64','float64']).columns
categorical_features = normal_data.select_dtypes(include=['object']).columns
# Preprocessing pipeline: scale numerics, one-hot encode categoricals
preprocessor = ColumnTransformer([
("num", StandardScaler(), numeric_features),
("cat", OneHotEncoder(handle_unknown="ignore", sparse=False), categorical_features)
])
# Fit only on normal traffic
X_normal = preprocessor.fit_transform(normal_data)
# Train-validation split
X_train, X_val = train_test_split(X_normal, test_size=0.2, random_state=42)
print("Training data shape:", X_train.shape)
print("Validation data shape:", X_val.shape)
輸出:
Training data shape: (160000, 71)Validation data shape: ( 40000, 71)
標籤被丟棄,只選擇良性樣本,即 i == 0。共有 37 個數值特徵,其中 4 個分類特徵經過獨熱編碼,構成總共 71 個輸入維度。
步驟 4. 定義最佳化去噪自編碼器
我們在輸入中新增高斯噪聲,以強制網路學習穩健的特徵。批次歸一化可以穩定訓練,而較小的瓶頸層(16 個單元)則有助於形成緊湊的潛在表徵。
input_dim = X_train. shape [1] inp = layers.Input(shape=(input_dim,)) noisy = layers. GaussianNoise(0.1)(inp) # Corrupt input slightly # Encoder x = layers.Dense(64, activation='relu')(noisy) x = layers. BatchNormalization()(x) # Stabilize training bottleneck = layers.Dense(16, activation='relu')(x) # Decoder x = layers.Dense(64, activation='relu')(bottleneck) x = layers. BatchNormalization()(x) out = layers.Dense(input_dim, activation='linear')(x) # Use linear for standardized input autoencoder = Model(inputs=inp, outputs=out) autoencoder. compile(optimizer='adam', loss='mse') autoencoder.summary()
輸出:
Model: "model"_________________________________________________________________Layer (type) Output Shape Param #=================================================================input_1 (InputLayer) [(None, 71)] 0gaussian_noise (GaussianNoise) (None, 71) 0dense (Dense) (None, 64) 4,608batch_normalization (BatchNormalization) (None, 64) 128dense_1 (Dense) (None, 16) 1,040dense_2 (Dense) (None, 64) 1,088batch_normalization_1 (BatchNormalization) (None, 64) 128dense_3 (Dense) (None, 71) 4,615=================================================================Total params: 11,607 Trainable params: 11,351 Non-trainable params: 256 _________________________________________________________________
步驟 5. 使用早期停止法訓練模型
# Early stopping to avoid overfitting
es = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)
print("Training started...")
history = autoencoder.fit (
X_train, X_train,
epochs=50,
batch_size=512, # larger batch for faster training
validation_data=(X_val, X_val),
shuffle=True,
callbacks=[es]
)
print ("Training completed!")
訓練損失曲線
plt.plot(history.history['loss'], label='Train Loss')
plt.plot(history.history['val_loss'], label='Val Loss')
plt.xlabel("Epochs")
plt.ylabel("MSE Loss")
plt.legend()
plt.title("Training vs Validation Loss")
plt.show()
輸出:
Training started...Epoch 1/50313/313 [==============================] - 2s 6ms/step - loss: 0.0254 - val_loss: 0.0181Epoch 2/50313/313 [==============================] - 2s 6ms/step - loss: 0.0158 - val_loss: 0.0145Epoch 3/50313/313 [==============================] - 2s 6ms/step - loss: 0.0123 - val_loss: 0.0127Epoch 4/50313/313 [==============================] - 2s 6ms/step - loss: 0.0106 - val_loss: 0.0108Epoch 5/50313/313 [==============================] - 2s 6ms/step - loss: 0.0094 - val_loss: 0.0097Epoch 6/50313/313 [==============================] - 2s 6ms/step - loss: 0.0086 - val_loss: 0.0085Epoch 7/50313/313 [==============================] - 2s 6ms/step - loss: 0.0082 - val_loss: 0.0083Epoch 8/50313/313 [==============================] - 2s 6ms/step - loss: 0.0080 - val_loss: 0.0086Restoring model weights from the end of the best epoch: 7.Epoch 00008: early stoppingTraining completed!
步驟 6. 零日漏洞檢測
# Transform datasets
X_normal_test = preprocessor.transform(normal_data)
X_zero_day_test = preprocessor.transform(zero_day_data)
# Compute reconstruction errors
recon_normal = np.mean(np.square(X_normal_test - autoencoder.predict(X_normal_test, batch_size=512)), axis=1)
recon_zero = np.mean(np.square(X_zero_day_test - autoencoder.predict(X_zero_day_test, batch_size=512)), axis=1)
# Threshold: 95th percentile of normal errors
threshold = np.percentile(recon_normal, 95)
print("Threshold:", threshold)
print("False Alarm Rate (Normal flagged as anomaly):", np.mean(recon_normal > threshold))
print("Detection Rate (Zero-Day detected):", np.mean(recon_zero > threshold))
輸出:
Threshold: 0.0121False Alarm Rate (normal→anomaly): 0.0480Detection Rate (Shellcode zero-day): 0.9150
我們將閾值設定為良性流量錯誤的第 95 個百分位數。4.8% 的正常流量被標記為誤報,而大約 91.5% 的 Shellcode 流量超過閾值並被正確識別為真報。
步驟 7. 視覺化
重建誤差直方圖
plt. figure(figsize=(8,5))
plt.hist(recon_normal, bins=50, alpha=0.6, label="Normal")
plt.hist(recon_zero, bins=50, alpha=0.6, label="Zero-Day (Shellcode)")
plt.axvline(threshold, color='red', linestyle='--', label='Threshold')
plt.xlabel("Reconstruction Error")
plt.ylabel("Frequency")
plt.legend()
plt.title("Normal vs Zero-Day Error Distribution")
plt.show()
輸出:

良性(藍色)和零日(橙色)流量的重建誤差疊加直方圖
ROC 曲線
y_true = np.concatenate([np.zeros_like(recon_normal), np.ones_like(recon_zero)])
y_scores = np.concatenate([recon_normal, recon_zero])
fpr, tpr, _ = roc_curve(y_true, y_scores)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, label=f"AUC = {roc_auc:.2f}")
plt.plot([0,1],[0,1],'--')
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.legend()
plt.title("ROC Curve for Zero-Day Detection")
plt.show()
輸出:

ROC 曲線展示了真陽性率與假陽性率;AUC = 0.93。
侷限性
其侷限性如下:
- DAE 可以檢測異常,但無法對攻擊型別進行分類。
- 選擇合適的閾值取決於資料集的選擇,並且可能需要進行微調。
- 在僅使用正常流量進行訓練時效果最佳。
關鍵要點
- 降噪自編碼器能夠有效檢測未見零日攻擊。
- 使用批歸一化、更大的批次大小和提前停止訓練可以提高訓練穩定性。
- 視覺化(損失曲線、誤差直方圖、ROC)使模型行為易於解釋。
小結
本教程演示瞭如何使用 UNSW-NB15 資料集,使用降噪自編碼器檢測網路流量中的零日攻擊。透過學習正常流量的穩健模式,該模型可以標記未見攻擊資料中的異常。 DAE 本身為現代入侵檢測系統提供了堅實的基礎,並且可以與先進的架構或監督分類器相結合,構建全面的入侵檢測系統。

評論留言