Spaces:
Sleeping
Sleeping
change evaluate ip way
Browse files
app.py
CHANGED
|
@@ -53,10 +53,19 @@ ACTION_MAP = {
|
|
| 53 |
log_history = []
|
| 54 |
initial_df_abn = pd.DataFrame(columns=["timestamp", "ip_address", "cookie_id", "account", "role", "action", "status"])
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
# --- 1. 載入模型與歷史資料 ---
|
| 57 |
try:
|
| 58 |
le_action = joblib.load('le_action.pkl')
|
| 59 |
-
le_ip = joblib.load('le_ip.pkl')
|
| 60 |
|
| 61 |
print("📂 正在處理靜態訓練基底資料...")
|
| 62 |
static_file = 'static_train_data.csv'
|
|
@@ -103,11 +112,8 @@ try:
|
|
| 103 |
baseline_df['day'] = baseline_df['timestamp'].dt.day
|
| 104 |
baseline_df['hour'] = baseline_df['timestamp'].dt.hour
|
| 105 |
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
unknown_ips = set(clean_ips) - known_ips
|
| 109 |
-
if unknown_ips:
|
| 110 |
-
le_ip.classes_ = np.append(le_ip.classes_, list(unknown_ips))
|
| 111 |
|
| 112 |
actions = baseline_df['action'].astype(str).values
|
| 113 |
known_actions = set(le_action.classes_)
|
|
@@ -115,16 +121,17 @@ try:
|
|
| 115 |
if unknown_actions:
|
| 116 |
le_action.classes_ = np.append(le_action.classes_, list(unknown_actions))
|
| 117 |
|
|
|
|
| 118 |
baseline_X = np.column_stack((
|
| 119 |
baseline_df['month'].values,
|
| 120 |
baseline_df['day'].values,
|
| 121 |
baseline_df['hour'].values,
|
| 122 |
-
|
| 123 |
le_action.transform(actions)
|
| 124 |
))
|
| 125 |
|
| 126 |
AI_READY = True
|
| 127 |
-
print("✅ AI 模組與動態歷史基準資料載入成功!")
|
| 128 |
except Exception as e:
|
| 129 |
AI_READY = False
|
| 130 |
global_stats["total_count"] = 0
|
|
@@ -277,17 +284,16 @@ def explain_abnormal_log(log_text):
|
|
| 277 |
except Exception as e: return f"⚠️ 呼叫 API 發生錯誤: {str(e)}"
|
| 278 |
|
| 279 |
# --- 2. 核心邏輯與真實 HDBSCAN 異常偵測 ---
|
| 280 |
-
# 🌟 修改:加入 gw (地理), fw (頻率) 接收參數
|
| 281 |
def check_anomaly(ip, action, log_time, ow, tw, iw, dw, gw, fw):
|
| 282 |
if not AI_READY: return "success"
|
| 283 |
if action in ["login_attempt", "logout"]: return "success"
|
| 284 |
|
| 285 |
try:
|
| 286 |
ow, tw, iw, dw = float(ow), float(tw), float(iw), float(dw)
|
| 287 |
-
gw, fw = float(gw), float(fw)
|
| 288 |
|
| 289 |
-
|
| 290 |
-
ip_code = float(
|
| 291 |
action_code = float(le_action.transform([action])[0])
|
| 292 |
|
| 293 |
current_time = pd.to_datetime(str(log_time))
|
|
@@ -335,9 +341,11 @@ def check_anomaly(ip, action, log_time, ow, tw, iw, dw, gw, fw):
|
|
| 335 |
min_distance = np.min(distances)
|
| 336 |
max_tolerance_distance = time_multiplier * 15.0 + 1.0
|
| 337 |
|
| 338 |
-
#
|
| 339 |
-
|
| 340 |
-
|
|
|
|
|
|
|
| 341 |
|
| 342 |
if min_distance > max_tolerance_distance:
|
| 343 |
return "⚠️異常_作息或行為不符"
|
|
@@ -369,11 +377,10 @@ def record_log(ip, cookie, account, role, action, status, time_mode, custom_time
|
|
| 369 |
if status == "success" and AI_READY:
|
| 370 |
try:
|
| 371 |
current_time = pd.to_datetime(str(log_time))
|
| 372 |
-
clean_ip = str(ip).split(" ")[0]
|
| 373 |
-
if clean_ip not in le_ip.classes_: le_ip.classes_ = np.append(le_ip.classes_, clean_ip)
|
| 374 |
if action not in le_action.classes_: le_action.classes_ = np.append(le_action.classes_, action)
|
| 375 |
-
|
| 376 |
-
|
|
|
|
| 377 |
baseline_X = np.vstack((baseline_X, new_row))
|
| 378 |
except: pass
|
| 379 |
|
|
@@ -528,12 +535,12 @@ with gr.Blocks(title="模擬校園入口與資安防護系統") as demo:
|
|
| 528 |
action_plot = gr.Plot(show_label=False)
|
| 529 |
|
| 530 |
with gr.Accordion("▼ 防護強度微調 (AI 敏感度)", open=True, elem_classes="weight-card"):
|
| 531 |
-
gr.Markdown("<span style='color:gray; font-size:0.9em;'>手動輸入 0~100 的數值。系統
|
| 532 |
with gr.Row():
|
| 533 |
overall_weight = gr.Number(label="總體防護強度 (%)", value=75, minimum=0, maximum=100)
|
| 534 |
time_weight = gr.Number(label="時間特徵權重 (%)", value=80, minimum=0, maximum=100)
|
|
|
|
| 535 |
ip_weight = gr.Number(label="IP 聲譽權重 (%)", value=65, minimum=0, maximum=100)
|
| 536 |
-
# 🌟 新增:將使用者要求的「地理偏移敏感度」與「行為頻率敏感度」加入介面中
|
| 537 |
with gr.Row():
|
| 538 |
device_weight = gr.Number(label="裝置指紋/行為權重 (%)", value=45, minimum=0, maximum=100)
|
| 539 |
geo_weight = gr.Number(label="🌍 地理偏移敏感度 (%)", value=50, minimum=0, maximum=100)
|
|
@@ -584,7 +591,6 @@ with gr.Blocks(title="模擬校園入口與資安防護系統") as demo:
|
|
| 584 |
time_mode.change(fn=toggle_time_input, inputs=time_mode, outputs=custom_time_input)
|
| 585 |
btn_explain_log.click(fn=explain_abnormal_log, inputs=[abnormal_log_input], outputs=[ai_explanation_output])
|
| 586 |
|
| 587 |
-
# 🌟 修改:將 geo_weight 與 freq_weight 正式加入全域的輸入變數清單
|
| 588 |
weight_inputs = [overall_weight, time_weight, ip_weight, device_weight, geo_weight, freq_weight]
|
| 589 |
update_outputs = [main_router, role_tabs, login_msg, welcome_text, store_df_all, store_df_abn, abnormal_log_input]
|
| 590 |
|
|
@@ -594,7 +600,6 @@ with gr.Blocks(title="模擬校園入口與資安防護系統") as demo:
|
|
| 594 |
fast_inputs = [mock_ip, mock_cookie, time_mode, custom_time_input] + weight_inputs
|
| 595 |
fast_outputs = update_outputs + [acc_input]
|
| 596 |
|
| 597 |
-
# 🌟 修改:更新所有按鈕的 Lambda 函式以接住並傳遞 gw, fw 參數
|
| 598 |
btn_fast_student.click(fn=lambda ip, c, tm, ct, ow, tw, iw, dw, gw, fw: fast_login("student", ip, c, tm, ct, ow, tw, iw, dw, gw, fw), inputs=fast_inputs, outputs=fast_outputs)
|
| 599 |
btn_fast_admin.click(fn=lambda ip, c, tm, ct, ow, tw, iw, dw, gw, fw: fast_login("admin", ip, c, tm, ct, ow, tw, iw, dw, gw, fw), inputs=fast_inputs, outputs=fast_outputs)
|
| 600 |
btn_toggle_role.click(fn=toggle_role, inputs=[acc_input, mock_ip, mock_cookie, time_mode, custom_time_input] + weight_inputs, outputs=[role_tabs, welcome_text, store_df_all, store_df_abn, abnormal_log_input, acc_input])
|
|
|
|
| 53 |
log_history = []
|
| 54 |
initial_df_abn = pd.DataFrame(columns=["timestamp", "ip_address", "cookie_id", "account", "role", "action", "status"])
|
| 55 |
|
| 56 |
+
# --- 🌟 新增:網段標籤轉換器 (Rule-based Feature Engineering) ---
|
| 57 |
+
def get_ip_category(ip_str):
|
| 58 |
+
ip = str(ip_str).split(" ")[0]
|
| 59 |
+
if ip.startswith("140.124."): return 0.0 # 信任網段:校內/宿舍
|
| 60 |
+
elif ip.startswith("61.228."): return 1.0 # 一般網段:國內家用
|
| 61 |
+
elif ip.startswith("210.61."): return 2.0 # 一般網段:公共場所
|
| 62 |
+
elif ip.startswith("45.33."): return 3.0 # 高風險網段:已知國外異常
|
| 63 |
+
else: return 4.0 # 未知網段:新出現的 IP
|
| 64 |
+
|
| 65 |
# --- 1. 載入模型與歷史資料 ---
|
| 66 |
try:
|
| 67 |
le_action = joblib.load('le_action.pkl')
|
| 68 |
+
# 🛑 移除舊的 le_ip = joblib.load('le_ip.pkl'),改用上方的新轉換器
|
| 69 |
|
| 70 |
print("📂 正在處理靜態訓練基底資料...")
|
| 71 |
static_file = 'static_train_data.csv'
|
|
|
|
| 112 |
baseline_df['day'] = baseline_df['timestamp'].dt.day
|
| 113 |
baseline_df['hour'] = baseline_df['timestamp'].dt.hour
|
| 114 |
|
| 115 |
+
# 🌟 修改:即時將資料表內的 IP 轉換為「網段標籤」陣列
|
| 116 |
+
ip_codes = np.array([get_ip_category(ip) for ip in baseline_df['clean_ip'].values])
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
actions = baseline_df['action'].astype(str).values
|
| 119 |
known_actions = set(le_action.classes_)
|
|
|
|
| 121 |
if unknown_actions:
|
| 122 |
le_action.classes_ = np.append(le_action.classes_, list(unknown_actions))
|
| 123 |
|
| 124 |
+
# 🌟 修改:將網段標籤組合進 AI 特徵矩陣中
|
| 125 |
baseline_X = np.column_stack((
|
| 126 |
baseline_df['month'].values,
|
| 127 |
baseline_df['day'].values,
|
| 128 |
baseline_df['hour'].values,
|
| 129 |
+
ip_codes,
|
| 130 |
le_action.transform(actions)
|
| 131 |
))
|
| 132 |
|
| 133 |
AI_READY = True
|
| 134 |
+
print("✅ AI 模組與動態歷史基準資料載入成功 (已套用智慧網段標籤)!")
|
| 135 |
except Exception as e:
|
| 136 |
AI_READY = False
|
| 137 |
global_stats["total_count"] = 0
|
|
|
|
| 284 |
except Exception as e: return f"⚠️ 呼叫 API 發生錯誤: {str(e)}"
|
| 285 |
|
| 286 |
# --- 2. 核心邏輯與真實 HDBSCAN 異常偵測 ---
|
|
|
|
| 287 |
def check_anomaly(ip, action, log_time, ow, tw, iw, dw, gw, fw):
|
| 288 |
if not AI_READY: return "success"
|
| 289 |
if action in ["login_attempt", "logout"]: return "success"
|
| 290 |
|
| 291 |
try:
|
| 292 |
ow, tw, iw, dw = float(ow), float(tw), float(iw), float(dw)
|
| 293 |
+
gw, fw = float(gw), float(fw)
|
| 294 |
|
| 295 |
+
# 🌟 修改:直接將輸入的 IP 透過函式轉成分類標籤
|
| 296 |
+
ip_code = float(get_ip_category(ip))
|
| 297 |
action_code = float(le_action.transform([action])[0])
|
| 298 |
|
| 299 |
current_time = pd.to_datetime(str(log_time))
|
|
|
|
| 341 |
min_distance = np.min(distances)
|
| 342 |
max_tolerance_distance = time_multiplier * 15.0 + 1.0
|
| 343 |
|
| 344 |
+
# 🌟 完成功能:將「IP 聲譽權重 (iw)」與網段標籤完美綁定!
|
| 345 |
+
# 如果是國外異常或未知 IP (標籤 3.0 或 4.0),且管理員調高了 iw
|
| 346 |
+
# 系統會嚴格縮減對它的距離容忍度,迫使它更容易被判定為異常
|
| 347 |
+
if ip_code >= 3.0:
|
| 348 |
+
max_tolerance_distance *= (1 - (iw / 100.0) * 0.6)
|
| 349 |
|
| 350 |
if min_distance > max_tolerance_distance:
|
| 351 |
return "⚠️異常_作息或行為不符"
|
|
|
|
| 377 |
if status == "success" and AI_READY:
|
| 378 |
try:
|
| 379 |
current_time = pd.to_datetime(str(log_time))
|
|
|
|
|
|
|
| 380 |
if action not in le_action.classes_: le_action.classes_ = np.append(le_action.classes_, action)
|
| 381 |
+
|
| 382 |
+
# 🌟 修改:寫入基準資料時,轉換為網段標籤
|
| 383 |
+
new_row = np.array([[float(current_time.month), float(current_time.day), float(current_time.hour), float(get_ip_category(ip)), float(le_action.transform([action])[0])]], dtype=float)
|
| 384 |
baseline_X = np.vstack((baseline_X, new_row))
|
| 385 |
except: pass
|
| 386 |
|
|
|
|
| 535 |
action_plot = gr.Plot(show_label=False)
|
| 536 |
|
| 537 |
with gr.Accordion("▼ 防護強度微調 (AI 敏感度)", open=True, elem_classes="weight-card"):
|
| 538 |
+
gr.Markdown("<span style='color:gray; font-size:0.9em;'>手動輸入 0~100 的數值。系統目前已成功啟用 IP 聲譽權重;地理與頻率的訓練架構僅為預留介面,須待升級後發揮。</span>")
|
| 539 |
with gr.Row():
|
| 540 |
overall_weight = gr.Number(label="總體防護強度 (%)", value=75, minimum=0, maximum=100)
|
| 541 |
time_weight = gr.Number(label="時間特徵權重 (%)", value=80, minimum=0, maximum=100)
|
| 542 |
+
# 🌟 已完成綁定
|
| 543 |
ip_weight = gr.Number(label="IP 聲譽權重 (%)", value=65, minimum=0, maximum=100)
|
|
|
|
| 544 |
with gr.Row():
|
| 545 |
device_weight = gr.Number(label="裝置指紋/行為權重 (%)", value=45, minimum=0, maximum=100)
|
| 546 |
geo_weight = gr.Number(label="🌍 地理偏移敏感度 (%)", value=50, minimum=0, maximum=100)
|
|
|
|
| 591 |
time_mode.change(fn=toggle_time_input, inputs=time_mode, outputs=custom_time_input)
|
| 592 |
btn_explain_log.click(fn=explain_abnormal_log, inputs=[abnormal_log_input], outputs=[ai_explanation_output])
|
| 593 |
|
|
|
|
| 594 |
weight_inputs = [overall_weight, time_weight, ip_weight, device_weight, geo_weight, freq_weight]
|
| 595 |
update_outputs = [main_router, role_tabs, login_msg, welcome_text, store_df_all, store_df_abn, abnormal_log_input]
|
| 596 |
|
|
|
|
| 600 |
fast_inputs = [mock_ip, mock_cookie, time_mode, custom_time_input] + weight_inputs
|
| 601 |
fast_outputs = update_outputs + [acc_input]
|
| 602 |
|
|
|
|
| 603 |
btn_fast_student.click(fn=lambda ip, c, tm, ct, ow, tw, iw, dw, gw, fw: fast_login("student", ip, c, tm, ct, ow, tw, iw, dw, gw, fw), inputs=fast_inputs, outputs=fast_outputs)
|
| 604 |
btn_fast_admin.click(fn=lambda ip, c, tm, ct, ow, tw, iw, dw, gw, fw: fast_login("admin", ip, c, tm, ct, ow, tw, iw, dw, gw, fw), inputs=fast_inputs, outputs=fast_outputs)
|
| 605 |
btn_toggle_role.click(fn=toggle_role, inputs=[acc_input, mock_ip, mock_cookie, time_mode, custom_time_input] + weight_inputs, outputs=[role_tabs, welcome_text, store_df_all, store_df_abn, abnormal_log_input, acc_input])
|