poc2_worked
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import base64
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from app.ndr import NDRAnalyzer, ThreatIntelManager
|
||||
from app.store import AlertStore
|
||||
|
||||
|
||||
class DummyRouterOS:
|
||||
configured = True
|
||||
|
||||
def list_dhcp_leases(self):
|
||||
return [{"address": "192.168.88.20", "mac": "AA:BB:CC:DD:EE:20", "hostname": "office-pc"}]
|
||||
|
||||
def list_arp(self):
|
||||
return [{"address": "192.168.88.30", "mac": "AA:BB:CC:DD:EE:30"}]
|
||||
|
||||
def block_ip(self, address, timeout_value, comment):
|
||||
raise AssertionError("auto-block is disabled in this test")
|
||||
|
||||
|
||||
def test_threat_intel_materializes_suricata8_datasets_and_matches():
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
store = AlertStore(os.path.join(td, "ids.db"))
|
||||
ja3 = "0123456789abcdef0123456789abcdef"
|
||||
hassh = "fedcba9876543210fedcba9876543210"
|
||||
ja4 = "t13d1516h2_8daaf6152771_02713d6af862"
|
||||
store.add_ioc("203.0.113.7", "ip", source="test")
|
||||
store.add_ioc("bad.example", "domain", source="test")
|
||||
store.add_ioc(ja3, "ja3", source="test")
|
||||
store.add_ioc(ja4, "ja4", source="test")
|
||||
sha256 = "a" * 64
|
||||
store.add_ioc(hassh, "hassh", source="test")
|
||||
store.add_ioc(sha256, "sha256", source="test")
|
||||
|
||||
manager = ThreatIntelManager(store, os.path.join(td, "suricata"))
|
||||
counts = manager.sync_suricata_datasets()
|
||||
assert counts["ip"] == 1
|
||||
assert counts["domain"] == 1
|
||||
assert counts["ja3"] == 1
|
||||
assert counts["ja4"] == 1
|
||||
assert counts["hassh"] == 1
|
||||
assert counts["sha256"] == 1
|
||||
|
||||
state = os.path.join(td, "suricata")
|
||||
assert open(os.path.join(state, "ti-ips.lst"), encoding="ascii").read().strip() == "203.0.113.7"
|
||||
assert open(os.path.join(state, "ti-sha256.lst"), encoding="ascii").read().strip() == sha256
|
||||
for kind, value in (("domains", "bad.example"), ("ja3", ja3), ("ja4", ja4), ("hassh", hassh)):
|
||||
encoded = open(os.path.join(state, f"ti-{kind}.lst"), encoding="ascii").read().strip()
|
||||
assert base64.b64decode(encoded).decode() == value
|
||||
|
||||
rules = open(os.path.join(state, "threat-intel.rules"), encoding="utf-8").read()
|
||||
assert "sid:1000205" in rules and "ja3.hash" in rules
|
||||
assert "sid:1000206" in rules and "alert tls" in rules
|
||||
assert "sid:1000207" in rules and "alert quic" in rules
|
||||
assert "sid:1000208" in rules and "ssh.hassh" in rules
|
||||
assert "sid:1000209" in rules and "ssh.hassh.server" in rules
|
||||
assert "sid:1000210" in rules and "filesha256:ti-sha256.lst" in rules
|
||||
assert "sid:1000215" in rules and "alert smb" in rules
|
||||
assert "type string,load ti-ja3.lst" in rules
|
||||
|
||||
hits = manager.match({"dest_ip": "203.0.113.7", "dns_query": "sub.bad.example", "tls_ja3": ja3})
|
||||
assert {row["indicator_type"] for row in hits} >= {"ip", "domain", "ja3"}
|
||||
store.close()
|
||||
|
||||
|
||||
def test_ndr_correlates_multistage_risk_and_status():
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
store = AlertStore(os.path.join(td, "ids.db"))
|
||||
first = store.correlate_signal({
|
||||
"subject_ip": "192.168.88.10", "timestamp": "2026-08-15T08:00:00+00:00",
|
||||
"kind": "behavior", "stage": "recon", "risk": 45, "summary": "scan",
|
||||
"dest_ip": "192.168.88.11",
|
||||
})
|
||||
second = store.correlate_signal({
|
||||
"subject_ip": "192.168.88.10", "timestamp": "2026-08-15T08:01:00+00:00",
|
||||
"kind": "alert", "stage": "lateral-movement", "risk": 60, "summary": "SMB access",
|
||||
"dest_ip": "192.168.88.11",
|
||||
})
|
||||
assert first == second
|
||||
incident = store.ndr_incident(first)
|
||||
assert incident["risk_score"] == 70
|
||||
assert set(incident["stages"]) == {"recon", "lateral-movement"}
|
||||
assert store.set_ndr_incident_status(first, "closed") is True
|
||||
assert store.ndr_incident(first)["status"] == "closed"
|
||||
assert store.ndr_summary()["open_incidents"] == 0
|
||||
store.close()
|
||||
|
||||
|
||||
def test_routeros_inventory_enriches_assets():
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
store = AlertStore(os.path.join(td, "ids.db"))
|
||||
ti = ThreatIntelManager(store, os.path.join(td, "suricata"))
|
||||
analyzer = NDRAnalyzer(
|
||||
store, ti, DummyRouterOS(), "192.168.88.0/24", "", "1h",
|
||||
enabled=True, auto_block=False,
|
||||
)
|
||||
result = analyzer.sync_routeros_inventory()
|
||||
assert result == {"arp": 1, "dhcp": 1, "assets": 2}
|
||||
assets = {row["ip"]: row for row in store.assets(20)}
|
||||
assert assets["192.168.88.20"]["hostname"] == "office-pc"
|
||||
assert assets["192.168.88.20"]["mac"] == "AA:BB:CC:DD:EE:20"
|
||||
assert assets["192.168.88.30"]["mac"] == "AA:BB:CC:DD:EE:30"
|
||||
store.close()
|
||||
|
||||
|
||||
def test_repeated_ip_mac_changes_escalate_to_network_spoofing_and_anomalies_are_cooled_down():
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
store = AlertStore(os.path.join(td, "ids.db"))
|
||||
ti = ThreatIntelManager(store, os.path.join(td, "suricata"))
|
||||
analyzer = NDRAnalyzer(
|
||||
store, ti, DummyRouterOS(), "192.168.88.0/24", "", "1h",
|
||||
enabled=True, auto_block=False,
|
||||
)
|
||||
ip = "192.168.88.44"
|
||||
for idx, mac in enumerate((
|
||||
"AA:BB:CC:DD:EE:01",
|
||||
"AA:BB:CC:DD:EE:02",
|
||||
"AA:BB:CC:DD:EE:03",
|
||||
"AA:BB:CC:DD:EE:04",
|
||||
)):
|
||||
analyzer._process({
|
||||
"timestamp": f"2026-08-15T08:00:{idx:02d}+00:00",
|
||||
"type": "arp", "direction": "outbound", "src_ip": ip,
|
||||
"arp_src_ip": ip, "arp_src_mac": mac,
|
||||
}, None)
|
||||
|
||||
incidents = store.recent_ndr_incidents(20)
|
||||
incident = next(row for row in incidents if row["subject_ip"] == ip)
|
||||
assert "network-spoofing" in incident["stages"]
|
||||
assert int(incident["risk_score"]) >= 78
|
||||
|
||||
anomaly = {
|
||||
"timestamp": "2026-08-15T08:10:00+00:00", "type": "anomaly",
|
||||
"direction": "outbound", "src_ip": "192.168.88.55",
|
||||
"anomaly_event": "APPLAYER_WRONG_DIRECTION_FIRST_DATA",
|
||||
}
|
||||
analyzer._process(anomaly, None)
|
||||
anomaly["timestamp"] = "2026-08-15T08:10:10+00:00"
|
||||
analyzer._process(anomaly, None)
|
||||
anomaly_incident = next(row for row in store.recent_ndr_incidents(20) if row["subject_ip"] == "192.168.88.55")
|
||||
events = store.ndr_incident_events(int(anomaly_incident["id"]), 20)
|
||||
assert sum(1 for event in events if event["stage"] == "protocol-anomaly") == 1
|
||||
store.close()
|
||||
Reference in New Issue
Block a user