mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-16 20:55:17 +03:00
329 lines
11 KiB
Python
329 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
AI-Powered BEC Detection Engine
|
|
|
|
Combines NLP analysis, behavioral scoring, and impersonation
|
|
detection to identify Business Email Compromise attacks.
|
|
|
|
Usage:
|
|
python process.py detect --email-json email.json --baseline-file baselines.json
|
|
python process.py train-baseline --email-log emails.json --output baselines.json
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
import math
|
|
from dataclasses import dataclass, field, asdict
|
|
from collections import defaultdict, Counter
|
|
|
|
|
|
@dataclass
|
|
class BECAIResult:
|
|
"""AI-powered BEC detection result."""
|
|
from_address: str = ""
|
|
subject: str = ""
|
|
impostor_score: float = 0.0
|
|
nlp_score: float = 0.0
|
|
behavioral_score: float = 0.0
|
|
intent_class: str = ""
|
|
overall_score: float = 0.0
|
|
is_bec: bool = False
|
|
confidence: float = 0.0
|
|
action: str = ""
|
|
indicators: list = field(default_factory=list)
|
|
|
|
|
|
# NLP feature patterns
|
|
URGENCY_PATTERNS = [
|
|
(r'\burgent(ly)?\b', 3), (r'\bimmediately\b', 3), (r'\basap\b', 3),
|
|
(r'\btime.?sensitive\b', 2), (r'\btoday\b', 1), (r'\bright\s+now\b', 2),
|
|
(r'\bdeadline\b', 1), (r'\boverdue\b', 2), (r'\bcritical\b', 2),
|
|
]
|
|
|
|
SECRECY_PATTERNS = [
|
|
(r'\bconfidential\b', 2), (r'\bdo\s+not\s+(share|tell|discuss)\b', 3),
|
|
(r'\bkeep.*between\s+us\b', 3), (r'\bquietly\b', 2),
|
|
(r'\bdon.t\s+mention\b', 2), (r'\bprivate\s+matter\b', 2),
|
|
]
|
|
|
|
FINANCIAL_PATTERNS = [
|
|
(r'\bwire\s+transfer\b', 3), (r'\binvoice\b', 2), (r'\bpayment\b', 2),
|
|
(r'\bbank\s+(account|details|transfer)\b', 3), (r'\bgift\s+card\b', 4),
|
|
(r'\bbitcoin\b', 3), (r'\baccount\s+number\b', 2), (r'\bswift\b', 2),
|
|
]
|
|
|
|
AUTHORITY_PATTERNS = [
|
|
(r'\bi\s+need\s+you\s+to\b', 2), (r'\bi.m\s+in\s+a\s+meeting\b', 3),
|
|
(r'\bhandle\s+this\b', 2), (r'\bi\s+authorize\b', 2),
|
|
(r'\bapproved\s+by\s+me\b', 3), (r'\bdon.t\s+call\b', 2),
|
|
]
|
|
|
|
|
|
def compute_nlp_score(text: str) -> tuple:
|
|
"""Compute NLP-based BEC score from email text."""
|
|
text_lower = text.lower()
|
|
scores = {"urgency": 0, "secrecy": 0, "financial": 0, "authority": 0}
|
|
indicators = []
|
|
|
|
for pattern, weight in URGENCY_PATTERNS:
|
|
if re.search(pattern, text_lower):
|
|
scores["urgency"] += weight
|
|
indicators.append(f"Urgency: {pattern}")
|
|
|
|
for pattern, weight in SECRECY_PATTERNS:
|
|
if re.search(pattern, text_lower):
|
|
scores["secrecy"] += weight
|
|
indicators.append(f"Secrecy: {pattern}")
|
|
|
|
for pattern, weight in FINANCIAL_PATTERNS:
|
|
if re.search(pattern, text_lower):
|
|
scores["financial"] += weight
|
|
indicators.append(f"Financial: {pattern}")
|
|
|
|
for pattern, weight in AUTHORITY_PATTERNS:
|
|
if re.search(pattern, text_lower):
|
|
scores["authority"] += weight
|
|
indicators.append(f"Authority: {pattern}")
|
|
|
|
total = sum(scores.values())
|
|
normalized = min(total / 25.0, 1.0) # Normalize to 0-1
|
|
|
|
# Determine intent
|
|
intent = "unknown"
|
|
if scores["financial"] > 3:
|
|
intent = "payment_request"
|
|
elif scores["authority"] > 3 and scores["urgency"] > 2:
|
|
intent = "directive"
|
|
elif scores["secrecy"] > 2:
|
|
intent = "sensitive_request"
|
|
|
|
return normalized, intent, indicators
|
|
|
|
|
|
def compute_impostor_score(email_data: dict, vip_list: list = None) -> tuple:
|
|
"""Check for sender impersonation."""
|
|
score = 0.0
|
|
indicators = []
|
|
|
|
from_name = email_data.get("from_display_name", "").lower()
|
|
from_email = email_data.get("from", "")
|
|
reply_to = email_data.get("reply_to", "")
|
|
|
|
from_domain = ""
|
|
match = re.search(r'@([\w.-]+)', from_email)
|
|
if match:
|
|
from_domain = match.group(1).lower()
|
|
|
|
# VIP name match from external domain
|
|
if vip_list:
|
|
for vip in vip_list:
|
|
vip_name = vip.get("name", "").lower()
|
|
vip_domain = vip.get("domain", "").lower()
|
|
if vip_name and vip_name in from_name:
|
|
if from_domain and vip_domain and from_domain != vip_domain:
|
|
score += 0.5
|
|
indicators.append(
|
|
f"Display name '{from_name}' matches VIP from external domain"
|
|
)
|
|
|
|
# Reply-to mismatch
|
|
if reply_to:
|
|
reply_domain = ""
|
|
match = re.search(r'@([\w.-]+)', reply_to)
|
|
if match:
|
|
reply_domain = match.group(1).lower()
|
|
if reply_domain and from_domain and reply_domain != from_domain:
|
|
score += 0.3
|
|
indicators.append(f"Reply-to domain mismatch: {reply_domain} vs {from_domain}")
|
|
|
|
return min(score, 1.0), indicators
|
|
|
|
|
|
def compute_behavioral_score(email_data: dict, baseline: dict) -> tuple:
|
|
"""Score behavioral anomalies against baseline."""
|
|
score = 0.0
|
|
indicators = []
|
|
|
|
sender = email_data.get("from", "")
|
|
recipient = email_data.get("to", "")
|
|
hour = email_data.get("send_hour", -1)
|
|
|
|
sender_baseline = baseline.get(sender, {})
|
|
|
|
# Check if first-time sender to this recipient
|
|
known_recipients = sender_baseline.get("recipients", [])
|
|
if recipient and known_recipients and recipient not in known_recipients:
|
|
score += 0.3
|
|
indicators.append("First-time communication with this recipient")
|
|
|
|
# Check unusual sending time
|
|
typical_hours = sender_baseline.get("typical_hours", [])
|
|
if hour >= 0 and typical_hours and hour not in typical_hours:
|
|
score += 0.2
|
|
indicators.append(f"Unusual sending hour: {hour}:00")
|
|
|
|
# Check if request type is unusual for sender
|
|
typical_topics = sender_baseline.get("typical_topics", [])
|
|
subject = email_data.get("subject", "").lower()
|
|
if typical_topics:
|
|
financial = any(w in subject for w in ["payment", "wire", "invoice", "transfer"])
|
|
if financial and "financial" not in typical_topics:
|
|
score += 0.3
|
|
indicators.append("Financial request from sender who doesn't typically discuss finances")
|
|
|
|
return min(score, 1.0), indicators
|
|
|
|
|
|
def detect_bec_ai(email_data: dict, baseline: dict = None,
|
|
vip_list: list = None) -> BECAIResult:
|
|
"""Run full AI BEC detection pipeline."""
|
|
result = BECAIResult()
|
|
result.from_address = email_data.get("from", "")
|
|
result.subject = email_data.get("subject", "")
|
|
|
|
body = email_data.get("body", "")
|
|
full_text = f"{result.subject} {body}"
|
|
|
|
# NLP analysis
|
|
result.nlp_score, result.intent_class, nlp_indicators = compute_nlp_score(full_text)
|
|
result.indicators.extend(nlp_indicators)
|
|
|
|
# Impostor detection
|
|
result.impostor_score, imp_indicators = compute_impostor_score(email_data, vip_list or [])
|
|
result.indicators.extend(imp_indicators)
|
|
|
|
# Behavioral analysis
|
|
if baseline:
|
|
result.behavioral_score, beh_indicators = compute_behavioral_score(
|
|
email_data, baseline
|
|
)
|
|
result.indicators.extend(beh_indicators)
|
|
|
|
# Weighted aggregate score
|
|
weights = {"nlp": 0.35, "impostor": 0.40, "behavioral": 0.25}
|
|
result.overall_score = (
|
|
result.nlp_score * weights["nlp"] +
|
|
result.impostor_score * weights["impostor"] +
|
|
result.behavioral_score * weights["behavioral"]
|
|
)
|
|
result.confidence = min(result.overall_score * 1.2, 1.0)
|
|
|
|
# Classification
|
|
if result.overall_score >= 0.7:
|
|
result.is_bec = True
|
|
result.action = "AUTO-QUARANTINE + SOC alert"
|
|
elif result.overall_score >= 0.5:
|
|
result.is_bec = True
|
|
result.action = "WARNING BANNER + analyst queue"
|
|
elif result.overall_score >= 0.3:
|
|
result.action = "WARNING BANNER only"
|
|
else:
|
|
result.action = "DELIVER normally"
|
|
|
|
return result
|
|
|
|
|
|
def train_baseline(emails: list) -> dict:
|
|
"""Build behavioral baselines from historical email data."""
|
|
baselines = defaultdict(lambda: {
|
|
"recipients": set(),
|
|
"typical_hours": [],
|
|
"typical_topics": set(),
|
|
"email_count": 0,
|
|
})
|
|
|
|
for email in emails:
|
|
sender = email.get("from", "")
|
|
if not sender:
|
|
continue
|
|
|
|
b = baselines[sender]
|
|
b["email_count"] += 1
|
|
|
|
recipient = email.get("to", "")
|
|
if recipient:
|
|
b["recipients"].add(recipient)
|
|
|
|
hour = email.get("send_hour", -1)
|
|
if hour >= 0:
|
|
b["typical_hours"].append(hour)
|
|
|
|
subject = email.get("subject", "").lower()
|
|
if any(w in subject for w in ["payment", "wire", "invoice"]):
|
|
b["typical_topics"].add("financial")
|
|
if any(w in subject for w in ["meeting", "schedule", "calendar"]):
|
|
b["typical_topics"].add("scheduling")
|
|
|
|
# Convert sets to lists for JSON serialization
|
|
result = {}
|
|
for sender, data in baselines.items():
|
|
hours = data["typical_hours"]
|
|
unique_hours = list(set(hours)) if hours else []
|
|
result[sender] = {
|
|
"recipients": list(data["recipients"]),
|
|
"typical_hours": unique_hours,
|
|
"typical_topics": list(data["typical_topics"]),
|
|
"email_count": data["email_count"],
|
|
}
|
|
|
|
return result
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="AI-Powered BEC Detection")
|
|
subparsers = parser.add_subparsers(dest="command")
|
|
|
|
detect_parser = subparsers.add_parser("detect", help="Detect BEC in email")
|
|
detect_parser.add_argument("--email-json", required=True)
|
|
detect_parser.add_argument("--baseline-file")
|
|
detect_parser.add_argument("--vip-file")
|
|
|
|
train_parser = subparsers.add_parser("train-baseline", help="Train behavioral baseline")
|
|
train_parser.add_argument("--email-log", required=True)
|
|
train_parser.add_argument("--output", required=True)
|
|
|
|
parser.add_argument("--json", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "detect":
|
|
with open(args.email_json) as f:
|
|
email_data = json.load(f)
|
|
baseline = {}
|
|
if args.baseline_file:
|
|
with open(args.baseline_file) as f:
|
|
baseline = json.load(f)
|
|
vip_list = []
|
|
if args.vip_file:
|
|
with open(args.vip_file) as f:
|
|
vip_list = json.load(f)
|
|
|
|
result = detect_bec_ai(email_data, baseline, vip_list)
|
|
if args.json:
|
|
print(json.dumps(asdict(result), indent=2))
|
|
else:
|
|
print(f"BEC Score: {result.overall_score:.0%}")
|
|
print(f"Is BEC: {'YES' if result.is_bec else 'No'}")
|
|
print(f"Intent: {result.intent_class}")
|
|
print(f"Action: {result.action}")
|
|
if result.indicators:
|
|
print("Indicators:")
|
|
for ind in result.indicators:
|
|
print(f" - {ind}")
|
|
|
|
elif args.command == "train-baseline":
|
|
with open(args.email_log) as f:
|
|
emails = json.load(f)
|
|
baseline = train_baseline(emails)
|
|
with open(args.output, 'w') as f:
|
|
json.dump(baseline, f, indent=2)
|
|
print(f"Baseline trained for {len(baseline)} senders")
|
|
|
|
else:
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|