絡(luò)攻擊檢測實戰(zhàn):從特征工程到隨機森林與Web部署)
簡介面向數(shù)據(jù)挖掘與網(wǎng)絡(luò)安全相關(guān)的課程設(shè)計與期末大作業(yè)場景這份基于KDD-CUP99數(shù)據(jù)集的網(wǎng)絡(luò)攻擊檢測項目提供了完整的Python實現(xiàn)和配套資料。項目源碼經(jīng)過本地編譯驗證難度適中內(nèi)容由助教老師審定可直接用于學(xué)習(xí)與驗收展示。壓縮包體積約6.93MB共75個文件核心包括14個Python腳本與10個pyc編譯文件同時含有7個Vue前端組件、4個CSS樣式、4個JSON配置、HTML入口頁面、Markdown說明文檔以及SQLite數(shù)據(jù)庫等覆蓋數(shù)據(jù)預(yù)處理、特征提取、模型訓(xùn)練與可視化展示的完整鏈路。前后端分離的目錄結(jié)構(gòu)清晰便于按模塊理解入侵檢測流程。已有349人瀏覽學(xué)習(xí)適合需要快速搭建網(wǎng)絡(luò)攻擊檢測系統(tǒng)原型、完成期末匯報或入門機器學(xué)習(xí)安全應(yīng)用的開發(fā)者參考。1. KDD-CUP99 網(wǎng)絡(luò)攻擊檢測一門課設(shè)如何把 IDS 從理論拉到工程大四做網(wǎng)絡(luò)攻擊檢測的項目老師遞過來一份基于 KDD-CUP99 數(shù)據(jù)集的源碼包。當(dāng)時我第一反應(yīng)是這東西太老——1999 年 DARPA 采集的 9 周網(wǎng)絡(luò)流量和今天動輒百 G 的骨干網(wǎng)不在同一個量級??烧姘阉鹜晡野l(fā)現(xiàn)這門課設(shè)考的不是算法多新而是三件事懂特征、會評估、能把模型包成系統(tǒng)。每個連接 41 維特征、四類攻擊DoS、Probe、R2L、U2R加一條正常標(biāo)簽這個分類框架二十多年后仍然是 IDS 行業(yè)的標(biāo)準(zhǔn)姿勢。適合期末大作業(yè)、畢設(shè)起步、以及想弄清入侵檢測數(shù)據(jù)鏈路怎么走的人。更難得的是這份資料把數(shù)據(jù)預(yù)處理、模型訓(xùn)練、Django 后端和 Vue 前端串成了一條完整鏈路不是那種只丟給你一個 notebook 的半成品。2. 數(shù)據(jù)先行的第一關(guān)讀懂 41 維特征再談分類怎么落地2.1 數(shù)據(jù)集結(jié)構(gòu)KDD-CUP99 的四類攻擊到底在檢測什么KDD-CUP99 每條記錄對應(yīng)一次 TCP 連接是一個 41 維特征加一條標(biāo)簽的 tabular 數(shù)據(jù)。41 維內(nèi)部可以分成四組第一組是基礎(chǔ)特征duration、protocol_type、service、flag、src_bytes、dst_bytes 等 9 個描述一條連接的時長、協(xié)議、上下行字節(jié)數(shù)第二組是內(nèi)容特征hot、failed_logins、num_compromised、root_shell 等 13 個主要設(shè)計用來捕捉 U2R 和 R2L 攻擊在會話里留下的痕跡第三組是基于時間的流量特征以 2 秒窗口統(tǒng)計相同目標(biāo)主機的連接次數(shù)、錯誤比例第四組是基于主機的流量特征窗口擴大成 100 條連接。四組特征測的是不同粒度的行為畫像。你如果只用前 9 個特征拿到 40% 準(zhǔn)確率都屬于正常——攻擊檢驗大量分布在流量統(tǒng)計特征上。標(biāo)簽側(cè)項目把攻擊歸成四類加正常共五類。DoS 是最常見的拒絕服務(wù)樣本量最大Probe 是端口掃描與漏洞探測屬于偵查行為R2L 是遠(yuǎn)程未授權(quán)訪問U2R 是本地提權(quán)這兩類樣本占比極小但恰恰是一般分類器最容易漏掉的部分。做這項網(wǎng)絡(luò)攻擊檢測我建議先把標(biāo)簽分布打印出來再往下走這一步?jīng)Q定你后面要不要做重采樣也決定答辯時你能不能講清楚為什么某些攻擊類型檢測不出來。2.2 預(yù)處理代碼字符串編碼、數(shù)值標(biāo)準(zhǔn)化與標(biāo)簽映射把 CSV 讀成矩陣并不難麻煩的是類別特征。protocol_typetcp/udp/icmp、servicehttp/ftp/…幾十種取值、flagSF/REJ/S0/…都是字符串?dāng)?shù)值列里 src_bytes 能從 0 到上億不標(biāo)準(zhǔn)化會讓樹模型之外的算法直接崩。下面是資源的預(yù)處理核心邏輯我用 sklearn Pipeline 重構(gòu)過一版import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import OneHotEncoder, StandardScaler, LabelEncoder from sklearn.compose import ColumnTransformer # 假設(shè) train_df 是讀入的原始 DataFrame列包含 41 個特征 label num_cols [duration, src_bytes, dst_bytes, wrong_fragment, urgent, hot, num_failed_logins, num_compromised, root_shell, su_attempted, num_root, num_file_creations, num_shells, num_access_files, num_outbound_cmds, count, srv_count, serror_rate, srv_serror_rate, rerror_rate, srv_rerror_rate, same_srv_rate, diff_srv_rate, srv_diff_host_rate, dst_host_count, dst_host_srv_count, dst_host_same_srv_rate, dst_host_diff_srv_rate, dst_host_same_src_port_rate, dst_host_srv_diff_host_rate, dst_host_serror_rate, dst_host_srv_serror_rate, dst_host_rerror_rate, dst_host_srv_rerror_rate] cat_cols [protocol_type, service, flag] X train_df.drop(label, axis1) y LabelEncoder().fit_transform(train_df[label]) preprocessor ColumnTransformer([ (num, StandardScaler(), num_cols), (cat, OneHotEncoder(handle_unknownignore), cat_cols) ]) X_processed preprocessor.fit_transform(X) # 嚴(yán)格流程先 fit_transform 訓(xùn)練集保存 preprocessor # 之后對測試集只調(diào)用 preprocessor.transform(X_test)這段代碼里最容易踩的坑是 pipeline 的 fit 邊界。ColumnTransformer 里 StandardScaler 和 OneHotEncoder 必須只在訓(xùn)練集上 fit之后測試集復(fù)用同一個 preprocessor。如果你把全量數(shù)據(jù)丟進(jìn)去然后一次性劃分均值、方差和類別字典都包含了未來信息交叉驗證分?jǐn)?shù)會虛高換到真實流量上立刻現(xiàn)形。handle_unknownignore 是為了測試集里出現(xiàn)訓(xùn)練集沒見過的 service 值時能安全跳過編碼矩陣?yán)镞@一列會全部為 0。標(biāo)簽映射上原始文件里是字符串normal、neptune、satan…LabelEncoder 會把它們壓成 0-4 的整數(shù)配合后面可視化時你需要在代碼里維護(hù)一張真實名字的對照表不要靠數(shù)字猜攻擊類型。我做網(wǎng)絡(luò)攻擊檢測時習(xí)慣把 normal 定為 0后面分別對應(yīng) DoS、Probe、R2L、U2R。源碼里自帶的清洗腳本已經(jīng)處理過行尾點號這些臟數(shù)據(jù)我復(fù)現(xiàn)時基本只改了下文件路徑。2.3 看一眼類別分布再動手不平衡比模型更重要訓(xùn)練集包含了幾十萬條記錄但分布極其傾斜DoS 占了大頭U2R 只有幾十到幾百條。這意味著即使什么都不學(xué)把所有樣本預(yù)測成 DoS整體準(zhǔn)確率也能到 60% 以上。所以預(yù)處理階段我會額外做一件事——分層抽樣stratify把各類比例在訓(xùn)練集/驗證集里保持一致X_train, X_val, y_train, y_val train_test_split( X_processed, y, test_size0.2, stratifyy, random_state42 )stratify 參數(shù)在樣本不均衡時屬于必選否則運氣差一點驗證集里可能一條 U2R 都沒有模型報告直接失去參考意義。分完層之后我還會用 value_counts() 核對每類樣本數(shù)U2R 如果少于 10 條說明這份數(shù)據(jù)切得太狠考慮換成 k 折分層交叉驗證或者把 10% 訓(xùn)練集和 corrected 測試集合并起來重新劃分。重采樣方案先放后面先用類權(quán)重class_weightbalanced跑通基線U2R/R2L 實在拉不起來再上 SMOTE別一上來就生成合成樣本不然邊界容易更亂。3. 模型主線隨機森林跑五分類準(zhǔn)確率不是唯一指標(biāo)3.1 為什么選樹模型而不是一上來就上神經(jīng)網(wǎng)絡(luò)KDD-CUP99 這類表格型數(shù)據(jù)里樹模型有天然優(yōu)勢對特征尺度和單調(diào)變換不敏感特征交互靠規(guī)則分裂就能表達(dá)訓(xùn)練速度快到可以在筆記本上反復(fù)試參數(shù)。相比之下深度學(xué)習(xí)最出效果的是高維稀疏或序列數(shù)據(jù)在這里往往需要大量調(diào)參才能追平隨機森林的基線。資源源碼里用的方案正是 sklearn 的 RandomForestClassifier配合少量特征篩選效果足夠支撐一場答辯。我在復(fù)現(xiàn)時保留了數(shù)據(jù)集原始特征組合沒有做 PCA。原因是 PCA 會把單個特征的可解釋性抹掉后面答辯講「為什么 count 和 srv_count 組合能識別端口掃描」時根本說不出人話。模型的 joblib 文件從源碼里直接導(dǎo)出來后是十幾 MB 級別Django 后端加載完全可行這也是我當(dāng)時沒用 XGBoost 的一個考量。如果你后面想換 LightGBM數(shù)據(jù)鏈路不用動只換訓(xùn)練腳本和模型文件即可這也是這套工程結(jié)構(gòu)比較舒服的地方。3.2 訓(xùn)練主代碼參數(shù)邊界與 OOB 驗證隨機森林有兩個參數(shù)最容易糾結(jié)n_estimators 和 max_depth。n_estimators 太少會抖太多訓(xùn)練慢且收益遞減max_depth 限制過深會過擬合但樹模型在 41 維特征上即使不限制也通常不會太離譜。我在源碼基礎(chǔ)上跑過的配置如下from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix import joblib model RandomForestClassifier( n_estimators200, # 我習(xí)慣先固定 200看 OOB 是否收斂 max_depth20, # 限制深度防止單棵樹記住噪聲 min_samples_split5, # 節(jié)點再劃分所需最少樣本數(shù)控制分裂風(fēng)險 class_weightbalanced, # 緩解 U2R/R2L 的傾斜 n_jobs-1, # 全核并行 random_state42, oob_scoreTrue # 用袋外樣本評估泛化不用額外切驗證集 ) model.fit(X_train, y_train) print(OOB score:, model.oob_score_) joblib.dump(model, ids_model.joblib) joblib.dump(preprocessor, preprocessor.joblib)這里的 oob_scoreTrue 是白撿的泛化參考每棵樹只用約三分之二樣本訓(xùn)練剩下三分之一袋外樣本做預(yù)測對隨機森林來說等價于內(nèi)置了一次交叉驗證。我一般拿它和驗證集分?jǐn)?shù)對照兩者差超過兩個百分點就說明有泄漏或過擬合。class_weightbalanced 會按類別頻率反比放大少數(shù)類權(quán)重代價是 DoS 的精確率可能輕微下降但從「攻擊檢測」的目標(biāo)看值得。model 和 preprocessor 分開保存有個好處接口層換模型不影響預(yù)處理改模型不改代碼。3.3 評估指標(biāo)該怎么看混淆矩陣?yán)锊刂鴨栴}一個典型翻車現(xiàn)場是整體準(zhǔn)確率 99%但 U2R 的 recall 是 0。原因很簡單U2R 樣本太少分類器把所有樣本都判成 Normal 或 DoS全局 accuracy 幾乎不受影響。應(yīng)對方案是打印 per-class 報告再談優(yōu)化y_pred model.predict(X_val) report classification_report( y_val, y_pred, target_names[Normal, DoS, Probe, R2L, U2R], digits3 ) print(report) print(confusion_matrix(y_val, y_pred))對于本項目的四類攻擊場景重點看兩個數(shù)R2L 和 U2R 的 recall。只要這兩個不過半系統(tǒng)在真實場景就約等于沒裝。Probe 和 DoS 通常不難難的是少數(shù)類。如果 val 集上 U2R recall 實在拉不起來先確認(rèn)是不是分層抽樣抽到了極少數(shù)樣本再考慮在 pipeline 里給少數(shù)類擴樣。模型訓(xùn)練完我會順手把當(dāng)時的參數(shù)和 per-class 指標(biāo)寫進(jìn)一個 JSON 文件跟模型放同目錄以后換數(shù)據(jù)集再跑至少知道這個基線是怎么來的這個習(xí)慣幫我避免了好幾次「這個分?jǐn)?shù)怎么復(fù)現(xiàn)不出來」的尷尬。4. Django Vue 雙端聯(lián)動把模型包成能演示的檢測系統(tǒng)4.1 Django 端模型加載與預(yù)測接口設(shè)計資源工程里 Django 目錄承擔(dān)后端服務(wù)manage.py 在根目錄配置里注冊了數(shù)據(jù)庫sqlite3static 和 templates 負(fù)責(zé)傳統(tǒng)服務(wù)端渲染頁面。既然前端用 Vue 寫了 net-analyze 這個工程實際聯(lián)調(diào)時我會讓 Django 只暴露 API頁面交給 Vue 去拉。預(yù)測接口的核心是避免重復(fù)加載模型——sklearn 的 joblib.load 在機械硬盤上動不動幾百毫秒放進(jìn) view 函數(shù)里每次請求都執(zhí)行接口延遲會高到難以接受。項目源碼里我看到的是把模型加載放在模塊層這個習(xí)慣建議保留# views.py import json import joblib import numpy as np from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt _model None _pre None def _load_model(): global _model, _pre if _model is None: _model joblib.load(ids_model.joblib) _pre joblib.load(preprocessor.joblib) csrf_exempt def predict_api(request): if request.method ! POST: return JsonResponse({error: need POST}, status405) data json.loads(request.body) features data.get(features) if len(features) ! 41: return JsonResponse({error: feature length must be 41}, status422) _load_model() x _pre.transform([features]) y _model.predict(x)[0] proba _model.predict_proba(x)[0].tolist() return JsonResponse({ prediction: int(y), probabilities: proba })注意這里我把 _load_model() 的調(diào)用放在拿到請求并校驗完字段之后才執(zhí)行是懶加載寫法。第一次請求慢一點后續(xù)請求全部復(fù)用內(nèi)存中的模型對象。URL 配置很簡單path(api/predict/, views.predict_api)。校驗 features 長度是一個低成本高收益的約定前端傳錯維度時接口直接返回 422而不是讓模型去拋一個讓人看不懂的維度錯誤。另外sqlite3 數(shù)據(jù)庫我用來記錄每次預(yù)測的日志包括時間、原始特征、預(yù)測結(jié)果后面畫趨勢圖不用重新跑模型。4.2 Vue 端儀表盤、攻擊分布圖與跨域聯(lián)調(diào)net-analyze 的 Vue 工程包含 babel.config.js、vue.config.js、jsconfig.json 和 package.json是標(biāo)準(zhǔn) Vue CLI 結(jié)構(gòu)。前端頁面要做的事有三件上傳或粘貼連接特征、調(diào)用 Django 接口拿預(yù)測結(jié)果、把結(jié)果渲染成攻擊分布圖。核心調(diào)用代碼并不復(fù)雜// src/api/detect.js import axios from axios export function detectAttack(features) { return axios.post(/api/predict/, { features }) .then(res res.data) .catch(err { console.error(predict failed:, err.message) throw err }) }這里的關(guān)鍵是開發(fā)環(huán)境不能直接寫死http://127.0.0.1:8000否則會撞上跨域。項目在 vue.config.js 里配了 devServer 代理把 /api 前綴轉(zhuǎn)發(fā)給 Django// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } } }配好 proxy 之后前端 axios 請求 /api/predict/ 時瀏覽器看到的請求是同源的由開發(fā)服務(wù)器轉(zhuǎn)發(fā)給后端跨域被化解在 dev server 這一層。jsconfig.json 里通常還有 開頭的路徑別名import 時可以寫/api/detect而不是一串相對路徑。babel.config.js 保持默認(rèn)即可如果你在項目里用了比較新的 JavaScript 語法preset-env 會自動做降級處理。4.3 前后端數(shù)據(jù)的字段約定與圖表渲染圖表部分我建議前端自己做聚合后端不要塞一堆 label 映射給前端。后端只返回整數(shù)預(yù)測結(jié)果和概率數(shù)組前端維護(hù)一張攻擊類型對照表。用 ECharts 的話餅圖和柱狀圖可以直接消費 probabilities 數(shù)組。做網(wǎng)絡(luò)攻擊檢測演示時把 DoS 和 Probe 的概率畫成懸浮條是答辯時的加分項——評審一眼就能看出系統(tǒng)不是黑匣子。這套字段約定里最容易出問題的是「前端和后端各維護(hù)一份攻擊類型映射表」時間一長兩邊必然對不上。我習(xí)慣以 Django 端的一個常量類為唯一事實源前端從接口拿類型名稱而不是自己寫死。整套源碼包從預(yù)處理腳本到 Vue 頁面是一次完整閉環(huán)換掉模型文件就能復(fù)用到別的數(shù)據(jù)集上這也是我愿意把它完整跑一遍的原因。5. 避坑實錄從數(shù)據(jù)泄漏到跨域預(yù)檢五個翻車點逐一拆解5.1 one-hot 編碼導(dǎo)致特征數(shù)量對不上現(xiàn)象訓(xùn)練時準(zhǔn)確率正常模型保存后新數(shù)據(jù)進(jìn)來transform 報出特征數(shù)量不符或者預(yù)測結(jié)果全亂。原因預(yù)處理用 pd.get_dummies 直接對全量數(shù)據(jù)編碼如果新數(shù)據(jù)里某個 service 值沒有出現(xiàn)在訓(xùn)練集那么新數(shù)據(jù)編碼后的列數(shù)和訓(xùn)練時不一致模型無法對齊特征矩陣。解決統(tǒng)一走 sklearn 的 OneHotEncoder在 preprocessor 里 fit 訓(xùn)練數(shù)據(jù)之后所有新數(shù)據(jù)都用同一個 transform。參數(shù) handle_unknownignore 會跳過新類別而不是報錯代價是這一列的編碼全部為 0但至少接口不會崩。5.2 StandardScaler 在全量數(shù)據(jù)上 fit造成信息泄漏現(xiàn)象交叉驗證分?jǐn)?shù) 99%但把模型放到 corrected 測試集或新流量上檢測率掉到 80% 以下。原因StandardScaler 的均值和方差在全量數(shù)據(jù)上計算測試數(shù)據(jù)的信息早已進(jìn)入訓(xùn)練流程。這在課設(shè)里最隱蔽因為分?jǐn)?shù)很好看很難察覺。解決把預(yù)處理器和模型綁在同一個 Pipeline 里對訓(xùn)練集 fit對驗證集/測試集只 transform。我復(fù)現(xiàn)時把 preprocessor 和 model 分開保存只是為了接口方便但 fit 邊界必須嚴(yán)格這是一個原則問題。5.3 整體準(zhǔn)確率 99%查 R2L/U2R 的召回率卻是 0現(xiàn)象classification_report 里前三行都漂亮后兩行全是 0看起來像模型壞了。原因類別嚴(yán)重不平衡max_depth 又設(shè)得比較淺樹的分裂完全被 Normal 和 DoS 主導(dǎo)少數(shù)類樣本一片葉子都分不到。單獨優(yōu)化整體 accuracy 會掩蓋這個問題。解決先用 class_weightbalanced 跑一遍看 U2R/R2L 的 recall 有沒有變化不行再對訓(xùn)練集做 SMOTE 過采樣。注意 SMOTE 必須在 train_test_split 之后做只對訓(xùn)練集生成合成樣本否則又是一輪泄漏。如果項目方只要整體準(zhǔn)確率好看那另說但做入侵檢測的人應(yīng)該知道這個坑。5.4 Django 接口每次請求都重新加載模型響應(yīng)要 3 秒現(xiàn)象第一次請求慢還能忍之后每次都慢看日志發(fā)現(xiàn) joblib.load 被反復(fù)執(zhí)行。原因模型加載寫在 view 函數(shù)體內(nèi)Django 每個請求都會重新走一遍機械硬盤上加載一個十幾 MB 的模型當(dāng)然慢。解決把模型加載提升到模塊頂層或者用 lru_cache 包裹加載函數(shù)進(jìn)程啟動時加載一次之后只讀內(nèi)存。另外如果開著 debug 模式代碼變更會自動重啟進(jìn)程也會讓人覺得模型加載慢部署時記得關(guān)掉 autoreload。5.5 Vue 聯(lián)調(diào)報跨域OPTIONS 預(yù)檢請求失敗現(xiàn)象前端 axios 請求 Django 接口控制臺報 CORS errorNetwork 面板里有一個失敗的 OPTIONS 請求。原因Django 默認(rèn)不允許跨域前端直連 127.0.0.1:8000 時瀏覽器先發(fā)預(yù)檢請求被后端拒絕。有人會在 settings.py 里悶頭加 django-cors-headers其實開發(fā)期更穩(wěn)妥的是用 vue.config.js 的 proxy。解決開發(fā)環(huán)境配 devServer.proxy把 /api 轉(zhuǎn)發(fā)到后端如果一定要跨域直連再裝 django-cors-headers在 CORS_ALLOWED_ORIGINS 里明確寫 http://localhost:8080。寫 * 在課設(shè)演示里能跑通但答辯被問安全策略時很難圓。6. 進(jìn)階把單次檢測變成一張能交代的攻擊態(tài)勢圖6.1 做一個輕量級的檢測回放腳本單次 POST 只能驗證一條記錄但答辯和實際使用需要看到整體效果。我會額外寫一個回放腳本讀取測試集的一條條記錄把預(yù)測結(jié)果按攻擊類型和時間窗口聚合生成 JSON 給前端畫趨勢圖# replay.py import json, joblib import pandas as pd from collections import Counter model joblib.load(ids_model.joblib) pre joblib.load(preprocessor.joblib) df pd.read_csv(corrected.csv) result [] for _, row in df.head(5000).iterrows(): x pre.transform([row[:41].tolist()])[0] result.append(int(model.predict([x])[0])) # 按窗口聚合出攻擊類型占比 print(Counter(result))這個腳本的價值不在效果而在于把「檢測系統(tǒng)」從單接口演示延伸成批量回放工具之后想畫準(zhǔn)確率曲線、誤報隨時間變化都從這個結(jié)果文件出發(fā)。6.2 閾值自整定讓概率輸出可調(diào)同樣一條記錄把判斷閾值從 0.7 調(diào)到 0.3結(jié)果會完全不同。接口里我還保留了一個 threshold 參數(shù)前端滑塊改閾值后端只把概率數(shù)組返回由前端決定是否判定為攻擊。這是模型 demo 里最容易被忽略的一個交互點加了它演示時就能現(xiàn)場展示「閾值調(diào)低 → 檢出率上升、誤報也上升」的關(guān)系比空口講概念有用得多。從那以后我每次做這類課設(shè)都會強制自己走一遍「先看標(biāo)簽分布 → 再定評估口徑 → 最后寫接口」的順序。數(shù)據(jù)沒看清楚之前模型調(diào)得再花哨都是白搭。希望幫到你。本文還有配套的精品資源點擊獲取