Spaces:
Sleeping
Sleeping
update locate weight
Browse files
app.py
CHANGED
|
@@ -33,20 +33,17 @@ global_stats = {
|
|
| 33 |
"start_time": datetime.datetime.now()
|
| 34 |
}
|
| 35 |
|
| 36 |
-
# 系統防護權重全域變數 (預設值)
|
| 37 |
system_weights = {
|
| 38 |
"ow": 75.0, "tw": 80.0, "iw": 65.0,
|
| 39 |
"dw": 45.0, "gw": 50.0, "fw": 60.0
|
| 40 |
}
|
| 41 |
|
| 42 |
-
# 專屬更新權重的副程式
|
| 43 |
def update_system_weights(ow, tw, iw, dw, gw, fw):
|
| 44 |
system_weights.update({
|
| 45 |
"ow": float(ow), "tw": float(tw), "iw": float(iw),
|
| 46 |
"dw": float(dw), "gw": float(gw), "fw": float(fw)
|
| 47 |
})
|
| 48 |
|
| 49 |
-
# --- 13 個系統按鈕的對應字典 ---
|
| 50 |
ACTION_MAP = {
|
| 51 |
"教務系統: 期末網路教學評量": "click_eval_system",
|
| 52 |
"教務系統: 期末網路預選系統": "click_pre_select_system",
|
|
@@ -66,24 +63,33 @@ ACTION_MAP = {
|
|
| 66 |
log_history = []
|
| 67 |
initial_df_abn = pd.DataFrame(columns=["timestamp", "ip_address", "cookie_id", "account", "role", "action", "status"])
|
| 68 |
|
|
|
|
| 69 |
def get_ip_category(ip_str):
|
| 70 |
ip = str(ip_str).split(" ")[0]
|
| 71 |
if ip.startswith("140.124."): return 0.0
|
| 72 |
elif ip.startswith("61.228."): return 1.0
|
| 73 |
elif ip.startswith("210.61."): return 2.0
|
| 74 |
elif ip.startswith("45.33."): return 3.0
|
| 75 |
-
else: return 4.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
# --- 1. 載入模型與歷史資料 ---
|
| 78 |
try:
|
| 79 |
le_action = joblib.load('le_action.pkl')
|
| 80 |
|
| 81 |
print("📂 正在讀取本地歷史資料庫...")
|
| 82 |
-
# 直接讀取本地的 baseline_logs.csv,不再向雲端同步
|
| 83 |
if os.path.exists('baseline_logs.csv'):
|
| 84 |
baseline_df = pd.read_csv('baseline_logs.csv')
|
| 85 |
else:
|
| 86 |
-
# 如果檔案不存在,建立帶有標題的空 DataFrame 並存檔
|
| 87 |
baseline_df = pd.DataFrame(columns=["timestamp", "ip_address", "cookie_id", "account", "role", "action", "status"])
|
| 88 |
baseline_df.to_csv('baseline_logs.csv', index=False)
|
| 89 |
|
|
@@ -179,7 +185,6 @@ def get_plot_data(selected_key):
|
|
| 179 |
|
| 180 |
df_live = pd.DataFrame(log_history)
|
| 181 |
if not df_live.empty:
|
| 182 |
-
# 👉 雙重防護 2:加入 errors='coerce' 容錯機制,剔除壞資料避免圖表破圖
|
| 183 |
df_live['timestamp'] = pd.to_datetime(df_live['timestamp'], errors='coerce')
|
| 184 |
df_live = df_live.dropna(subset=['timestamp'])
|
| 185 |
df_live = df_live[df_live['action'] == action_code].copy()
|
|
@@ -264,7 +269,6 @@ def check_anomaly(ip, account, action, log_time):
|
|
| 264 |
if action in ["login_attempt", "logout"]: return "success"
|
| 265 |
|
| 266 |
try:
|
| 267 |
-
# 直接從全域變數提取當前權重
|
| 268 |
ow = float(system_weights["ow"])
|
| 269 |
tw = float(system_weights["tw"])
|
| 270 |
iw = float(system_weights["iw"])
|
|
@@ -272,9 +276,22 @@ def check_anomaly(ip, account, action, log_time):
|
|
| 272 |
gw = float(system_weights["gw"])
|
| 273 |
fw = float(system_weights["fw"])
|
| 274 |
|
| 275 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
current_dt = pd.to_datetime(str(log_time))
|
| 277 |
-
cutoff_dt = current_dt - pd.Timedelta(seconds=60)
|
| 278 |
|
| 279 |
recent_clicks = 0
|
| 280 |
for log in log_history:
|
|
@@ -300,7 +317,7 @@ def check_anomaly(ip, account, action, log_time):
|
|
| 300 |
if (recent_clicks + 1) > max_allowed_clicks:
|
| 301 |
return f"⚠️異常_單一操作頻率過高 ({recent_clicks+1}次/分)"
|
| 302 |
|
| 303 |
-
# --- 第
|
| 304 |
ip_code = float(get_ip_category(ip))
|
| 305 |
action_code = float(le_action.transform([action])[0])
|
| 306 |
|
|
@@ -362,13 +379,11 @@ def check_anomaly(ip, account, action, log_time):
|
|
| 362 |
def record_log(ip, cookie, account, role, action, status, time_mode, custom_time):
|
| 363 |
global baseline_X
|
| 364 |
|
| 365 |
-
# 👉 雙重防護 1:驗證自訂時間格式
|
| 366 |
if time_mode == "自訂時間 (模擬過去/未來)" and custom_time and str(custom_time).strip() != "":
|
| 367 |
try:
|
| 368 |
valid_time = pd.to_datetime(str(custom_time))
|
| 369 |
log_time = valid_time.strftime("%Y-%m-%d %H:%M:%S")
|
| 370 |
except Exception:
|
| 371 |
-
# 🚨 解析失敗,退回當前時間並標記狀態
|
| 372 |
log_time = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8))).strftime("%Y-%m-%d %H:%M:%S")
|
| 373 |
status = "⚠️格式錯誤_無效的自訂時間"
|
| 374 |
else:
|
|
@@ -380,7 +395,6 @@ def record_log(ip, cookie, account, role, action, status, time_mode, custom_time
|
|
| 380 |
|
| 381 |
new_log = {"timestamp": log_time, "ip_address": ip, "cookie_id": cookie, "account": account, "role": role, "action": action, "status": status}
|
| 382 |
|
| 383 |
-
# 將產生的新 Log 直接 append (附加) 到本地的 baseline_logs.csv 中
|
| 384 |
new_df = pd.DataFrame([new_log])
|
| 385 |
new_df.to_csv('baseline_logs.csv', mode='a', header=not os.path.exists('baseline_logs.csv'), index=False)
|
| 386 |
|
|
@@ -388,7 +402,7 @@ def record_log(ip, cookie, account, role, action, status, time_mode, custom_time
|
|
| 388 |
except: pass
|
| 389 |
|
| 390 |
global_stats["total_count"] += 1
|
| 391 |
-
if "異常" in str(status) or "錯誤" in str(status): global_stats["abnormal_count"] += 1
|
| 392 |
|
| 393 |
log_history.insert(0, new_log)
|
| 394 |
|
|
@@ -401,10 +415,10 @@ def record_log(ip, cookie, account, role, action, status, time_mode, custom_time
|
|
| 401 |
except: pass
|
| 402 |
|
| 403 |
df_all = pd.DataFrame(log_history)
|
| 404 |
-
abnormal_logs = [log for log in log_history if "異常" in str(log.get("status", "")) or "錯誤" in str(log.get("status", ""))]
|
| 405 |
df_abnormal = pd.DataFrame(abnormal_logs) if abnormal_logs else pd.DataFrame(columns=df_all.columns)
|
| 406 |
|
| 407 |
-
input_update = gr.update(value=f"時間: {log_time} | IP: {ip} | 帳號: {account} | 動作: {action} | 狀態: {status}") if "異常" in str(status) or "錯誤" in str(status) else gr.update()
|
| 408 |
return df_all, df_abnormal, input_update
|
| 409 |
|
| 410 |
def fast_login(target_account, ip, cookie, time_mode, custom_time):
|
|
@@ -593,7 +607,6 @@ with gr.Blocks(title="模擬校園入口與資安防護系統") as demo:
|
|
| 593 |
time_mode.change(fn=toggle_time_input, inputs=time_mode, outputs=custom_time_input)
|
| 594 |
btn_explain_log.click(fn=explain_abnormal_log, inputs=[abnormal_log_input], outputs=[ai_explanation_output])
|
| 595 |
|
| 596 |
-
# 綁定權重拉桿與更新函式
|
| 597 |
weight_inputs = [overall_weight, time_weight, ip_weight, device_weight, geo_weight, freq_weight]
|
| 598 |
for w_input in weight_inputs:
|
| 599 |
w_input.change(fn=update_system_weights, inputs=weight_inputs, outputs=[])
|
|
|
|
| 33 |
"start_time": datetime.datetime.now()
|
| 34 |
}
|
| 35 |
|
|
|
|
| 36 |
system_weights = {
|
| 37 |
"ow": 75.0, "tw": 80.0, "iw": 65.0,
|
| 38 |
"dw": 45.0, "gw": 50.0, "fw": 60.0
|
| 39 |
}
|
| 40 |
|
|
|
|
| 41 |
def update_system_weights(ow, tw, iw, dw, gw, fw):
|
| 42 |
system_weights.update({
|
| 43 |
"ow": float(ow), "tw": float(tw), "iw": float(iw),
|
| 44 |
"dw": float(dw), "gw": float(gw), "fw": float(fw)
|
| 45 |
})
|
| 46 |
|
|
|
|
| 47 |
ACTION_MAP = {
|
| 48 |
"教務系統: 期末網路教學評量": "click_eval_system",
|
| 49 |
"教務系統: 期末網路預選系統": "click_pre_select_system",
|
|
|
|
| 63 |
log_history = []
|
| 64 |
initial_df_abn = pd.DataFrame(columns=["timestamp", "ip_address", "cookie_id", "account", "role", "action", "status"])
|
| 65 |
|
| 66 |
+
# HDBSCAN 特徵分群用的 IP 分類
|
| 67 |
def get_ip_category(ip_str):
|
| 68 |
ip = str(ip_str).split(" ")[0]
|
| 69 |
if ip.startswith("140.124."): return 0.0
|
| 70 |
elif ip.startswith("61.228."): return 1.0
|
| 71 |
elif ip.startswith("210.61."): return 2.0
|
| 72 |
elif ip.startswith("45.33."): return 3.0
|
| 73 |
+
else: return 4.0
|
| 74 |
+
|
| 75 |
+
# 👉 新增:專供 Geo-Weight 權重邏輯使用的精細分級
|
| 76 |
+
def get_geo_level(ip_str):
|
| 77 |
+
ip = str(ip_str).split(" ")[0]
|
| 78 |
+
if ip == "140.124.71.55": return 0 # 校內預設
|
| 79 |
+
elif ip == "140.124.18.22": return 1 # 宿舍
|
| 80 |
+
elif ip.startswith("61.228."): return 2 # 家裡
|
| 81 |
+
elif ip.startswith("210.61."): return 3 # 公共場所
|
| 82 |
+
elif ip.startswith("45.33."): return 4 # 國外異常
|
| 83 |
+
else: return 5 # 未知
|
| 84 |
|
| 85 |
# --- 1. 載入模型與歷史資料 ---
|
| 86 |
try:
|
| 87 |
le_action = joblib.load('le_action.pkl')
|
| 88 |
|
| 89 |
print("📂 正在讀取本地歷史資料庫...")
|
|
|
|
| 90 |
if os.path.exists('baseline_logs.csv'):
|
| 91 |
baseline_df = pd.read_csv('baseline_logs.csv')
|
| 92 |
else:
|
|
|
|
| 93 |
baseline_df = pd.DataFrame(columns=["timestamp", "ip_address", "cookie_id", "account", "role", "action", "status"])
|
| 94 |
baseline_df.to_csv('baseline_logs.csv', index=False)
|
| 95 |
|
|
|
|
| 185 |
|
| 186 |
df_live = pd.DataFrame(log_history)
|
| 187 |
if not df_live.empty:
|
|
|
|
| 188 |
df_live['timestamp'] = pd.to_datetime(df_live['timestamp'], errors='coerce')
|
| 189 |
df_live = df_live.dropna(subset=['timestamp'])
|
| 190 |
df_live = df_live[df_live['action'] == action_code].copy()
|
|
|
|
| 269 |
if action in ["login_attempt", "logout"]: return "success"
|
| 270 |
|
| 271 |
try:
|
|
|
|
| 272 |
ow = float(system_weights["ow"])
|
| 273 |
tw = float(system_weights["tw"])
|
| 274 |
iw = float(system_weights["iw"])
|
|
|
|
| 276 |
gw = float(system_weights["gw"])
|
| 277 |
fw = float(system_weights["fw"])
|
| 278 |
|
| 279 |
+
# 👉 第一道防線:地理圍籬 (Geo-Fencing) 攔截
|
| 280 |
+
geo_level = get_geo_level(ip)
|
| 281 |
+
|
| 282 |
+
if gw == 100 and geo_level > 0:
|
| 283 |
+
return "⚠️異常_地理位置受限 (100%絕對鎖定: 僅限校內專網存取)"
|
| 284 |
+
elif gw >= 90 and geo_level > 1:
|
| 285 |
+
return "⚠️異常_地理位置受限 (高敏感防護: 僅限校園網路與宿舍)"
|
| 286 |
+
elif gw >= 70 and geo_level > 2:
|
| 287 |
+
return "⚠️異常_地理位置受限 (進階防護: 禁止公共場所與海外連線)"
|
| 288 |
+
elif gw >= 50 and geo_level > 3:
|
| 289 |
+
return "⚠️異常_地理位置受限 (預設防護: 禁止海外異常 IP)"
|
| 290 |
+
# 若 gw < 50,則放行所有位置,交由後續 AI 處理
|
| 291 |
+
|
| 292 |
+
# --- 第二道防線:頻率攔截 ---
|
| 293 |
current_dt = pd.to_datetime(str(log_time))
|
| 294 |
+
cutoff_dt = current_dt - pd.Timedelta(seconds=60)
|
| 295 |
|
| 296 |
recent_clicks = 0
|
| 297 |
for log in log_history:
|
|
|
|
| 317 |
if (recent_clicks + 1) > max_allowed_clicks:
|
| 318 |
return f"⚠️異常_單一操作頻率過高 ({recent_clicks+1}次/分)"
|
| 319 |
|
| 320 |
+
# --- 第三道防線:HDBSCAN AI 空間異常判斷 ---
|
| 321 |
ip_code = float(get_ip_category(ip))
|
| 322 |
action_code = float(le_action.transform([action])[0])
|
| 323 |
|
|
|
|
| 379 |
def record_log(ip, cookie, account, role, action, status, time_mode, custom_time):
|
| 380 |
global baseline_X
|
| 381 |
|
|
|
|
| 382 |
if time_mode == "自訂時間 (模擬過去/未來)" and custom_time and str(custom_time).strip() != "":
|
| 383 |
try:
|
| 384 |
valid_time = pd.to_datetime(str(custom_time))
|
| 385 |
log_time = valid_time.strftime("%Y-%m-%d %H:%M:%S")
|
| 386 |
except Exception:
|
|
|
|
| 387 |
log_time = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8))).strftime("%Y-%m-%d %H:%M:%S")
|
| 388 |
status = "⚠️格式錯誤_無效的自訂時間"
|
| 389 |
else:
|
|
|
|
| 395 |
|
| 396 |
new_log = {"timestamp": log_time, "ip_address": ip, "cookie_id": cookie, "account": account, "role": role, "action": action, "status": status}
|
| 397 |
|
|
|
|
| 398 |
new_df = pd.DataFrame([new_log])
|
| 399 |
new_df.to_csv('baseline_logs.csv', mode='a', header=not os.path.exists('baseline_logs.csv'), index=False)
|
| 400 |
|
|
|
|
| 402 |
except: pass
|
| 403 |
|
| 404 |
global_stats["total_count"] += 1
|
| 405 |
+
if "異常" in str(status) or "錯誤" in str(status) or "受限" in str(status): global_stats["abnormal_count"] += 1
|
| 406 |
|
| 407 |
log_history.insert(0, new_log)
|
| 408 |
|
|
|
|
| 415 |
except: pass
|
| 416 |
|
| 417 |
df_all = pd.DataFrame(log_history)
|
| 418 |
+
abnormal_logs = [log for log in log_history if "異常" in str(log.get("status", "")) or "錯誤" in str(log.get("status", "")) or "受限" in str(log.get("status", ""))]
|
| 419 |
df_abnormal = pd.DataFrame(abnormal_logs) if abnormal_logs else pd.DataFrame(columns=df_all.columns)
|
| 420 |
|
| 421 |
+
input_update = gr.update(value=f"時間: {log_time} | IP: {ip} | 帳號: {account} | 動作: {action} | 狀態: {status}") if "異常" in str(status) or "錯誤" in str(status) or "受限" in str(status) else gr.update()
|
| 422 |
return df_all, df_abnormal, input_update
|
| 423 |
|
| 424 |
def fast_login(target_account, ip, cookie, time_mode, custom_time):
|
|
|
|
| 607 |
time_mode.change(fn=toggle_time_input, inputs=time_mode, outputs=custom_time_input)
|
| 608 |
btn_explain_log.click(fn=explain_abnormal_log, inputs=[abnormal_log_input], outputs=[ai_explanation_output])
|
| 609 |
|
|
|
|
| 610 |
weight_inputs = [overall_weight, time_weight, ip_weight, device_weight, geo_weight, freq_weight]
|
| 611 |
for w_input in weight_inputs:
|
| 612 |
w_input.change(fn=update_system_weights, inputs=weight_inputs, outputs=[])
|