初始提交:恶意 IP 特征+行为融合检测系统
- 特征检测(规则/签名)与行为检测(24h 统计)单进程运行 - 融合名单按首次发现时间倒序,展示位置/归属(GeoIP + ASN) - 钉钉融合推送(加签、24h 节流、去重) - 页面状态灯、CSV 导出、Docker 镜像构建文件
This commit is contained in:
commit
2d76ed6672
42 files changed
+10645
No files matched your search
@@ -0,0 +1,103 @@
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
|
||||
from graylog_ops import watch # noqa: E402
|
||||
|
||||
NOW = "2026-10-17T16:00:00Z"
|
||||
|
||||
|
||||
def _behavior(ip, verdict="建议封禁", score=75):
|
||||
return {
|
||||
"ip": ip, "score": score, "verdict": verdict,
|
||||
"signals": ["高失败率"], "reason_text": "24 小时内扫描了大量路径。",
|
||||
"share_note": "", "behavior_type": "目录扫描",
|
||||
"asn": "4538", "asn_org": "教育网", "shared_type": "education",
|
||||
"total": 50, "pct4": 60.0, "uri_card": 20, "uri_templates": 15,
|
||||
"ua_card": 1, "host_card": 2, "coverage": 100.0, "peak_share": 50.0,
|
||||
"top_uri": "/.env", "top_ua": "curl/8.0",
|
||||
"country_code": "CN", "country_name": "中国", "region": "北京",
|
||||
"city": "Beijing",
|
||||
}
|
||||
|
||||
|
||||
class FusedTests(unittest.TestCase):
|
||||
def test_feature_and_behavior_merge(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
store = watch.Store(os.path.join(directory, "ips.db"))
|
||||
try:
|
||||
store.upsert({"ip": "203.0.113.9", "tier": watch.TIER_MALICIOUS,
|
||||
"score": 91.0, "total": 120, "window": 3600,
|
||||
"rules": {"probe_java": 10},
|
||||
"reasons": ["探测 10 次"]}, NOW)
|
||||
store.sync_fused(NOW)
|
||||
fused = store.fused_items()
|
||||
self.assertEqual(len(fused), 1)
|
||||
self.assertEqual(fused[0]["sources"], "feature")
|
||||
self.assertEqual(fused[0]["first_banned_at"], NOW)
|
||||
self.assertIn("特征命中", fused[0]["fused_reason"])
|
||||
|
||||
# behaviour-only ban is merged
|
||||
store.replace_behavior_profiles([_behavior("198.51.100.9")])
|
||||
store.sync_fused("2026-10-17T16:10:00Z")
|
||||
merged = {item["ip"]: item for item in store.fused_items()}
|
||||
self.assertIn("198.51.100.9", merged)
|
||||
self.assertEqual(merged["198.51.100.9"]["sources"], "behavior")
|
||||
self.assertEqual(merged["203.0.113.9"]["sources"], "feature")
|
||||
|
||||
# same IP on both sides becomes "both"
|
||||
store.upsert({"ip": "198.51.100.9", "tier": watch.TIER_MALICIOUS,
|
||||
"score": 80.0, "total": 50, "window": 3600,
|
||||
"rules": {"probe_java": 6},
|
||||
"reasons": ["探测 6 次"]}, "2026-10-17T16:11:00Z")
|
||||
store.sync_fused("2026-10-17T16:12:00Z")
|
||||
merged = {item["ip"]: item for item in store.fused_items()}
|
||||
self.assertEqual(merged["198.51.100.9"]["sources"], "both")
|
||||
|
||||
# watch-only behaviour must not enter the block list
|
||||
store.replace_behavior_profiles([
|
||||
_behavior("192.0.2.44", verdict="仅告警(共享出口)")])
|
||||
store.sync_fused("2026-10-17T16:20:00Z")
|
||||
ips = {item["ip"] for item in store.fused_items()}
|
||||
self.assertNotIn("192.0.2.44", ips)
|
||||
self.assertIn("203.0.113.9", ips) # feature rows stay forever
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
def test_push_throttle_24h(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
store = watch.Store(os.path.join(directory, "ips.db"))
|
||||
try:
|
||||
now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
store.replace_behavior_profiles([_behavior("198.51.100.7")])
|
||||
store.sync_fused(now)
|
||||
# first sync suppresses historical rows; a fresh detection is due
|
||||
with store.lock, store.conn:
|
||||
store.conn.execute(
|
||||
"UPDATE ip_fused SET last_pushed_at=NULL WHERE ip=?",
|
||||
("198.51.100.7",))
|
||||
due = store.due_pushes(active_hours=24, cooldown_hours=24)
|
||||
self.assertEqual([row["ip"] for row in due], ["198.51.100.7"])
|
||||
|
||||
store.mark_pushed(["198.51.100.7"], now)
|
||||
self.assertEqual(store.due_pushes(24, 24), []) # throttled
|
||||
|
||||
old = time.strftime("%Y-%m-%dT%H:%M:%SZ",
|
||||
time.gmtime(time.time() - 25 * 3600))
|
||||
with store.lock, store.conn:
|
||||
store.conn.execute(
|
||||
"UPDATE ip_fused SET last_pushed_at=? WHERE ip=?",
|
||||
(old, "198.51.100.7"))
|
||||
due = store.due_pushes(active_hours=24, cooldown_hours=24)
|
||||
self.assertEqual([row["ip"] for row in due], ["198.51.100.7"])
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user