npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
Adversary-in-the-Middle (AiTM) phishing attacks use reverse-proxy infrastructure to sit between the victim and the legitimate authentication service, intercepting both credentials and session cookies in real time. This allows attackers to bypass multi-factor authentication (MFA). The most prevalent PhaaS kits in 2025 include Tycoon 2FA, Sneaky 2FA, EvilProxy, and Evilginx. Over 1 million PhaaS attacks were detected in January-February 2025 alone. These attacks have evolved from QR codes to HTML attachments and SVG files for link distribution.
When to Use
- When conducting security assessments that involve performing adversary in the middle phishing detection
- When following incident response procedures for related security events
- When performing scheduled security testing or auditing activities
- When validating security controls through hands-on testing
Prerequisites
- Azure AD / Entra ID Conditional Access policies
- SIEM with authentication log ingestion (Azure AD sign-in logs)
- Web proxy with SSL inspection and URL categorization
- Endpoint Detection and Response (EDR) solution
- FIDO2/phishing-resistant MFA capability
Key Concepts
How AiTM Works
- Victim receives phishing email with link to attacker-controlled domain
- Attacker domain runs reverse proxy that mirrors legitimate login page
- Victim enters credentials on proxied page; credentials captured in transit
- Reverse proxy forwards credentials to real authentication service
- MFA challenge sent to victim; victim completes MFA on proxied page
- Attacker captures session cookie returned by legitimate service
- Attacker replays session cookie to access victim's account without MFA
Major AiTM Kits (2025)
| Kit | Type | Primary Targets | Evasion |
|---|---|---|---|
| Tycoon 2FA | PhaaS | Microsoft 365, Google | CAPTCHA, Cloudflare turnstile |
| EvilProxy | PhaaS | Microsoft 365, Google, Okta | Random URLs, IP rotation |
| Evilginx | Open-source | Any web application | Custom phishlets |
| Sneaky 2FA | PhaaS | Microsoft 365 | Anti-bot checks |
| NakedPages | PhaaS | Multiple | Minimal infrastructure |
Detection Indicators
- Authentication from unusual IP not matching user profile
- Session cookie reuse from different IP/device than authentication
- Login page served from non-Microsoft/non-Google infrastructure
- CDN requests to legitimate auth providers from phishing domains
- Impossible travel between authentication and session usage
Workflow
Step 1: Deploy Phishing-Resistant MFA
- Implement FIDO2 security keys or Windows Hello for Business for high-value accounts
- Configure Conditional Access to require phishing-resistant MFA for admins
- Enable certificate-based authentication where possible
- Disable SMS and voice MFA for privileged accounts
- AiTM cannot intercept FIDO2 because authentication is bound to origin domain
Step 2: Configure Conditional Access Policies
- Require compliant/managed device for sensitive application access
- Block authentication from anonymous proxies and Tor exit nodes
- Enforce token binding to limit session cookie replay
- Configure continuous access evaluation (CAE) for real-time token revocation
- Implement sign-in risk policies that require re-authentication for risky sign-ins
Step 3: Build AiTM Detection Rules
- Alert on sign-in followed by session from different IP within 10 minutes
- Detect authentication where proxy IP does not match user's expected location
- Monitor for impossible travel patterns in session usage
- Alert on inbox rules created immediately after authentication (common post-compromise)
- Detect new MFA method registration from suspicious sign-in
Step 4: Monitor Web Proxy for AiTM Infrastructure
- Log and analyze DNS queries to newly registered domains
- Detect connections to known PhaaS infrastructure IPs
- Alert on authentication page backgrounds loaded from legitimate CDNs through proxy domains
- Monitor for SSL certificates issued to domains mimicking corporate login pages
- Block access to known EvilProxy/Evilginx infrastructure via threat intelligence
Step 5: Implement Post-Compromise Detection
- Alert on mailbox forwarding rules created after suspicious authentication
- Detect OAuth app consent after AiTM sign-in
- Monitor for email sending patterns indicating BEC follow-up
- Alert on SharePoint/OneDrive mass download after session hijack
- Track lateral movement from compromised account
Tools & Resources
- Microsoft Entra ID Protection: Risk-based Conditional Access
- Azure AD Sign-in Logs: Authentication event analysis
- Okta ThreatInsight: AiTM proxy detection at IdP level
- Sekoia TDR: AiTM campaign tracking and intelligence
- Evilginx (defensive): Understanding attack mechanics for detection
Validation
- Phishing-resistant MFA blocks AiTM session capture in test scenario
- Conditional Access denies session replay from different device/IP
- SIEM alerts fire on simulated AiTM sign-in patterns
- Web proxy blocks connections to known PhaaS infrastructure
- Post-compromise rules detect inbox rule creation after suspicious auth
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md2.7 KB
Adversary-in-the-Middle (AiTM) Phishing Detection - API Reference
AiTM Attack Overview
AiTM phishing uses a reverse proxy between the victim and legitimate login page to intercept session cookies in real-time, bypassing MFA. Common frameworks: Evilginx2, Modlishka, Muraena.
Attack Chain:
- Victim clicks phishing link
- Reverse proxy forwards request to real login page
- Victim enters credentials and completes MFA
- Proxy captures session cookie
- Attacker replays session cookie from different location
Azure AD / Entra ID Sign-In Logs
Export via Microsoft Graph API
GET https://graph.microsoft.com/v1.0/auditLogs/signInsKey Fields
| Field | Type | Description |
|---|---|---|
userPrincipalName |
string | User email |
createdDateTime |
ISO-8601 | Sign-in timestamp |
ipAddress |
string | Source IP address |
location.latitude |
float | Geo-location latitude |
location.longitude |
float | Geo-location longitude |
deviceDetail.displayName |
string | Device name |
correlationId |
string | Session correlation ID |
userAgent |
string | Browser user agent |
Detection Methods
Impossible Travel
Calculates Haversine great-circle distance between consecutive logins. If distance / time > 900 km/h (commercial flight speed) and distance > 100km, flags as suspicious.
Suspicious Inbox Rules
AiTM attackers commonly create rules to:
- Forward emails to external address (
forwardTo,redirectTo) - Delete incoming emails (
moveToDeletedItems,permanentDelete) - Auto-read messages (
markAsRead) - Filter on keywords: invoice, payment, wire, bank, password
Token Replay Detection
Multiple IPs and devices in a short timeframe for the same user session indicates stolen session token replay.
Inbox Rules Format
[
{
"displayName": "rule1",
"mailboxOwner": "user@example.com",
"actions": {"forwardTo": [{"emailAddress": {"address": "attacker@evil.com"}}]},
"conditions": {"subjectContains": ["invoice", "payment"]},
"createdDateTime": "2024-01-15T10:00:00Z"
}
]Haversine Formula
from math import radians, cos, sin, asin, sqrt
def haversine_km(lat1, lon1, lat2, lon2):
lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
dlat, dlon = lat2 - lat1, lon2 - lon1
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
return 2 * 6371 * asin(sqrt(a))Output Schema
{
"report": "aitm_phishing_detection",
"total_sign_ins_analyzed": 5000,
"total_findings": 8,
"severity_summary": {"critical": 3, "high": 5},
"findings": [{"type": "impossible_travel", "severity": "critical"}]
}CLI Usage
python agent.py --logs signin_logs.json --inbox-rules rules.json --output report.jsonstandards.md1.4 KB
Standards & References: AiTM Phishing Detection
MITRE ATT&CK References
- T1557: Adversary-in-the-Middle
- T1539: Steal Web Session Cookie
- T1550.004: Use Alternate Authentication Material: Web Session Cookie
- T1566.002: Phishing: Spearphishing Link
- T1114.003: Email Collection: Email Forwarding Rule
- T1098.005: Account Manipulation: Device Registration
AiTM PhaaS Landscape (2025)
- Over 1 million PhaaS attacks detected in Jan-Feb 2025 (Barracuda)
- Tycoon 2FA most prevalent followed by EvilProxy and Evilginx
- Transition from QR codes to HTML attachments and SVG files
- Average time from compromise to inbox rule creation: under 30 minutes
Detection Rule Categories
| Rule | Data Source | Confidence |
|---|---|---|
| Session IP mismatch | Azure AD sign-in logs | High |
| Impossible travel | Azure AD + session logs | High |
| Inbox rule post-auth | Exchange audit logs | High |
| New MFA method post-risky-sign-in | Azure AD audit | Medium |
| OAuth consent post-auth | Azure AD audit | Medium |
| Proxy CDN pattern | Web proxy logs | Medium |
| New domain phishing page | DNS + CT logs | Low |
Phishing-Resistant MFA Standards
- FIDO2 WebAuthn: Origin-bound authentication prevents AiTM
- Certificate-Based Auth: Client certificate bound to device
- Windows Hello for Business: Hardware-bound credential
- NIST SP 800-63B AAL3: Phishing-resistant authenticator requirement
workflows.md1.9 KB
Workflows: AiTM Phishing Detection
Workflow 1: AiTM Attack Detection
User clicks phishing link
|
v
[Reverse proxy serves mirrored login page]
+-- Page loads assets from legitimate CDN
+-- SSL cert issued for lookalike domain
|
v
[User enters credentials + completes MFA]
|
v
[Attacker captures session cookie]
|
v
[DETECTION POINTS]
+-- Web proxy: Connection to newly registered domain
+-- Azure AD: Sign-in from proxy IP (unfamiliar location)
+-- Session: Cookie replay from different IP within minutes
+-- Exchange: Inbox rule created post-authentication
+-- Azure AD: New OAuth app consent
|
v
[Automated response]
+-- Revoke all sessions for user
+-- Require re-authentication with phishing-resistant MFA
+-- Remove suspicious inbox rules
+-- Revoke OAuth app consents
+-- Block attacker IP at firewallWorkflow 2: AiTM Incident Response
AiTM compromise confirmed
|
v
[Immediate containment (first 30 minutes)]
+-- Revoke all user sessions and tokens
+-- Force password reset
+-- Remove all inbox forwarding rules
+-- Revoke OAuth app consents granted post-compromise
+-- Disable compromised MFA methods
|
v
[Investigation (next 2-4 hours)]
+-- Review Azure AD sign-in logs for compromise timeline
+-- Check email sent items for BEC/phishing sent from account
+-- Review SharePoint/OneDrive access for data exfiltration
+-- Check for lateral movement to other accounts
+-- Identify all affected users (same phishing campaign)
|
v
[Remediation]
+-- Enroll user in phishing-resistant MFA (FIDO2)
+-- Block phishing domain at email gateway and web proxy
+-- Retract phishing email from all mailboxes
+-- Update Conditional Access policies
+-- Notify all targeted users
|
v
[Post-incident]
+-- Add IOCs to threat intelligence
+-- Create SIEM detection rules for observed TTPs
+-- Update security awareness training
+-- Assess FIDO2 rollout for broader user populationScripts 2
agent.py6.9 KB
#!/usr/bin/env python3
"""Adversary-in-the-Middle (AiTM) Phishing Detection agent - analyzes sign-in
logs and inbox rules to detect AiTM phishing campaigns that bypass MFA by
proxying authentication sessions."""
import argparse
import json
from collections import Counter, defaultdict
from datetime import datetime
from pathlib import Path
from math import radians, cos, sin, asin, sqrt
def haversine_km(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""Calculate great-circle distance between two points."""
lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
dlat = lat2 - lat1
dlon = lon2 - lon1
a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
return 2 * 6371 * asin(sqrt(a))
def load_sign_in_logs(log_path: str) -> list[dict]:
"""Load Azure AD / Entra ID sign-in logs in JSON format."""
content = Path(log_path).read_text(encoding="utf-8")
try:
return json.loads(content)
except json.JSONDecodeError:
return [json.loads(line) for line in content.strip().splitlines() if line.strip()]
def detect_impossible_travel(logs: list[dict], max_speed_kmh: float = 900) -> list[dict]:
"""Detect impossible travel - logins from distant locations in short time."""
findings = []
user_logins = defaultdict(list)
for log in logs:
user = log.get("userPrincipalName", "")
ts = log.get("createdDateTime", "")
lat = log.get("location", {}).get("latitude")
lon = log.get("location", {}).get("longitude")
ip = log.get("ipAddress", "")
if user and ts and lat is not None and lon is not None:
try:
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
user_logins[user].append({"dt": dt, "lat": lat, "lon": lon, "ip": ip})
except ValueError:
continue
for user, logins in user_logins.items():
logins.sort(key=lambda x: x["dt"])
for i in range(1, len(logins)):
prev, curr = logins[i - 1], logins[i]
dist = haversine_km(prev["lat"], prev["lon"], curr["lat"], curr["lon"])
hours = (curr["dt"] - prev["dt"]).total_seconds() / 3600
if hours > 0 and dist / hours > max_speed_kmh and dist > 100:
findings.append({
"type": "impossible_travel",
"severity": "critical",
"user": user,
"distance_km": round(dist, 1),
"time_hours": round(hours, 2),
"speed_kmh": round(dist / hours, 0),
"from_ip": prev["ip"],
"to_ip": curr["ip"],
"detail": f"Login from {round(dist)}km away in {round(hours, 1)}h ({round(dist/hours)}km/h)",
})
return findings
def detect_suspicious_inbox_rules(rules_path: str) -> list[dict]:
"""Detect inbox rules commonly created by AiTM attackers."""
findings = []
rules = json.loads(Path(rules_path).read_text(encoding="utf-8"))
suspicious_actions = {"moveToDeletedItems", "permanentDelete", "forwardTo",
"redirectTo", "markAsRead"}
suspicious_keywords = {"invoice", "payment", "wire", "bank", "urgent",
"password", "mfa", "security", "verify"}
for rule in rules:
actions = set(rule.get("actions", {}).keys())
matched_actions = actions & suspicious_actions
conditions = json.dumps(rule.get("conditions", {})).lower()
matched_keywords = {kw for kw in suspicious_keywords if kw in conditions}
if matched_actions:
severity = "critical" if "forwardTo" in matched_actions or "redirectTo" in matched_actions else "high"
findings.append({
"type": "suspicious_inbox_rule",
"severity": severity,
"rule_name": rule.get("displayName", "unnamed"),
"user": rule.get("mailboxOwner", "unknown"),
"suspicious_actions": sorted(matched_actions),
"keyword_triggers": sorted(matched_keywords),
"created": rule.get("createdDateTime", ""),
"detail": f"Rule with {', '.join(matched_actions)} actions",
})
return findings
def detect_token_replay(logs: list[dict]) -> list[dict]:
"""Detect potential session token replay from new device/location."""
findings = []
user_sessions = defaultdict(list)
for log in logs:
user = log.get("userPrincipalName", "")
session_id = log.get("correlationId", "")
device = log.get("deviceDetail", {}).get("displayName", "unknown")
ip = log.get("ipAddress", "")
user_agent = log.get("userAgent", "")
if user:
user_sessions[user].append({
"session": session_id, "device": device,
"ip": ip, "ua": user_agent,
})
for user, sessions in user_sessions.items():
ips = set(s["ip"] for s in sessions)
devices = set(s["device"] for s in sessions)
if len(ips) > 3 and len(devices) > 3:
findings.append({
"type": "possible_token_replay",
"severity": "high",
"user": user,
"unique_ips": len(ips),
"unique_devices": len(devices),
"detail": f"{len(ips)} IPs and {len(devices)} devices in session data",
})
return findings
def generate_report(log_path: str, rules_path: str = None,
max_speed: float = 900) -> dict:
"""Run all detection checks and build consolidated report."""
logs = load_sign_in_logs(log_path)
findings = []
findings.extend(detect_impossible_travel(logs, max_speed))
findings.extend(detect_token_replay(logs))
if rules_path:
findings.extend(detect_suspicious_inbox_rules(rules_path))
severity_counts = Counter(f["severity"] for f in findings)
return {
"report": "aitm_phishing_detection",
"generated_at": datetime.utcnow().isoformat() + "Z",
"total_sign_ins_analyzed": len(logs),
"total_findings": len(findings),
"severity_summary": dict(severity_counts),
"findings": findings,
}
def main():
parser = argparse.ArgumentParser(description="AiTM Phishing Detection Agent")
parser.add_argument("--logs", required=True, help="Azure AD sign-in logs JSON file")
parser.add_argument("--inbox-rules", help="Inbox rules JSON export")
parser.add_argument("--max-speed", type=float, default=900, help="Max travel speed km/h (default: 900)")
parser.add_argument("--output", help="Output JSON file path")
args = parser.parse_args()
report = generate_report(args.logs, args.inbox_rules, args.max_speed)
output = json.dumps(report, indent=2)
if args.output:
Path(args.output).write_text(output, encoding="utf-8")
print(f"Report written to {args.output}")
else:
print(output)
if __name__ == "__main__":
main()
process.py10.8 KB
#!/usr/bin/env python3
"""
AiTM Phishing Detection Engine
Analyzes Azure AD sign-in logs and session data to detect
Adversary-in-the-Middle phishing attacks including session
cookie replay and impossible travel patterns.
Usage:
python process.py detect --signin-log signins.json
python process.py check-session --session-log sessions.json
python process.py analyze-domain --domain suspicious-login.com
"""
import argparse
import json
import re
import sys
import math
from dataclasses import dataclass, field, asdict
from datetime import datetime, timedelta
from collections import defaultdict
@dataclass
class AiTMIndicator:
"""An AiTM detection indicator."""
indicator_type: str = ""
description: str = ""
severity: str = "medium"
confidence: float = 0.0
user: str = ""
timestamp: str = ""
details: dict = field(default_factory=dict)
@dataclass
class AiTMAnalysis:
"""Complete AiTM analysis result."""
total_signins: int = 0
suspicious_signins: int = 0
session_replays_detected: int = 0
impossible_travel_detected: int = 0
post_compromise_indicators: int = 0
indicators: list = field(default_factory=list)
affected_users: list = field(default_factory=list)
recommended_actions: list = field(default_factory=list)
# Known AiTM infrastructure patterns
AITM_DOMAIN_PATTERNS = [
r'login.*microsoft.*\.(top|xyz|info|click|online)',
r'auth.*office.*\.(top|xyz|info|click|online)',
r'sso.*\.(top|xyz|info|click|online)',
r'verify.*account.*\.(top|xyz|info|click|online)',
r'.*\.workers\.dev$',
r'.*\.pages\.dev$',
r'.*-login-.*\.(com|net|org)',
]
# Known PhaaS hosting patterns
PHAAS_INFRA = [
'cloudflare-ipfs.com',
'workers.dev',
'pages.dev',
'web.app',
'firebaseapp.com',
'glitch.me',
'netlify.app',
'vercel.app',
]
def haversine_distance(lat1, lon1, lat2, lon2):
"""Calculate distance in km between two coordinates."""
R = 6371 # Earth radius in km
lat1_r, lat2_r = math.radians(lat1), math.radians(lat2)
dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = (math.sin(dlat / 2) ** 2 +
math.cos(lat1_r) * math.cos(lat2_r) * math.sin(dlon / 2) ** 2)
c = 2 * math.asin(math.sqrt(a))
return R * c
def detect_aitm_signins(signins: list) -> AiTMAnalysis:
"""Analyze sign-in logs for AiTM indicators."""
analysis = AiTMAnalysis()
analysis.total_signins = len(signins)
user_signins = defaultdict(list)
for signin in signins:
user = signin.get("userPrincipalName", "")
user_signins[user].append(signin)
affected = set()
for user, events in user_signins.items():
sorted_events = sorted(events, key=lambda x: x.get("createdDateTime", ""))
for i in range(len(sorted_events)):
event = sorted_events[i]
ip = event.get("ipAddress", "")
location = event.get("location", {})
risk_level = event.get("riskLevelDuringSignIn", "none")
is_interactive = event.get("isInteractive", True)
app = event.get("appDisplayName", "")
timestamp = event.get("createdDateTime", "")
# Check for anonymous proxy
if event.get("isFromAnonymousProxy", False):
analysis.indicators.append(AiTMIndicator(
indicator_type="anonymous_proxy",
description=f"Sign-in from anonymous proxy/VPN",
severity="high",
confidence=0.7,
user=user,
timestamp=timestamp,
details={"ip": ip}
))
analysis.suspicious_signins += 1
affected.add(user)
# Check for impossible travel
if i > 0:
prev = sorted_events[i - 1]
prev_loc = prev.get("location", {})
prev_time = prev.get("createdDateTime", "")
if (location.get("geoCoordinates") and
prev_loc.get("geoCoordinates")):
lat1 = prev_loc["geoCoordinates"].get("latitude", 0)
lon1 = prev_loc["geoCoordinates"].get("longitude", 0)
lat2 = location["geoCoordinates"].get("latitude", 0)
lon2 = location["geoCoordinates"].get("longitude", 0)
distance = haversine_distance(lat1, lon1, lat2, lon2)
try:
t1 = datetime.fromisoformat(prev_time.replace('Z', '+00:00'))
t2 = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
hours = (t2 - t1).total_seconds() / 3600
if hours > 0 and distance > 0:
speed = distance / hours
if speed > 900: # Faster than commercial flight
analysis.indicators.append(AiTMIndicator(
indicator_type="impossible_travel",
description=(
f"Impossible travel: {distance:.0f}km in "
f"{hours:.1f}h ({speed:.0f}km/h)"
),
severity="high",
confidence=0.85,
user=user,
timestamp=timestamp,
details={
"from_ip": prev.get("ipAddress"),
"to_ip": ip,
"distance_km": round(distance),
"speed_kmh": round(speed)
}
))
analysis.impossible_travel_detected += 1
affected.add(user)
except (ValueError, TypeError):
pass
# Check for session from different IP shortly after auth
if i < len(sorted_events) - 1:
next_event = sorted_events[i + 1]
next_ip = next_event.get("ipAddress", "")
next_time = next_event.get("createdDateTime", "")
if ip and next_ip and ip != next_ip:
try:
t1 = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
t2 = datetime.fromisoformat(next_time.replace('Z', '+00:00'))
minutes = (t2 - t1).total_seconds() / 60
if 0 < minutes < 10:
analysis.indicators.append(AiTMIndicator(
indicator_type="session_ip_switch",
description=(
f"Session IP changed within {minutes:.0f}min "
f"({ip} -> {next_ip})"
),
severity="critical",
confidence=0.9,
user=user,
timestamp=timestamp,
details={
"auth_ip": ip,
"session_ip": next_ip,
"time_delta_min": round(minutes)
}
))
analysis.session_replays_detected += 1
affected.add(user)
except (ValueError, TypeError):
pass
analysis.affected_users = list(affected)
if affected:
analysis.recommended_actions = [
"Revoke all sessions for affected users",
"Force password reset with phishing-resistant MFA",
"Check for inbox forwarding rules created post-compromise",
"Review OAuth app consents for affected accounts",
"Block source IPs at firewall",
"Retract phishing email from all mailboxes"
]
return analysis
def analyze_domain(domain: str) -> dict:
"""Check if domain matches known AiTM/PhaaS patterns."""
result = {
"domain": domain,
"is_suspicious": False,
"indicators": [],
"risk_score": 0
}
domain_lower = domain.lower()
for pattern in AITM_DOMAIN_PATTERNS:
if re.search(pattern, domain_lower):
result["indicators"].append(f"Matches AiTM domain pattern: {pattern}")
result["risk_score"] += 30
result["is_suspicious"] = True
for infra in PHAAS_INFRA:
if domain_lower.endswith(infra):
result["indicators"].append(f"Hosted on known PhaaS infrastructure: {infra}")
result["risk_score"] += 25
result["is_suspicious"] = True
# Check for brand impersonation in domain
brands = ['microsoft', 'office', 'outlook', 'google', 'okta', 'azure']
for brand in brands:
if brand in domain_lower and not domain_lower.endswith(f'.{brand}.com'):
result["indicators"].append(f"Contains brand name '{brand}' in non-official domain")
result["risk_score"] += 20
result["is_suspicious"] = True
result["risk_score"] = min(result["risk_score"], 100)
return result
def main():
parser = argparse.ArgumentParser(description="AiTM Phishing Detection")
subparsers = parser.add_subparsers(dest="command")
detect_parser = subparsers.add_parser("detect", help="Detect AiTM in sign-in logs")
detect_parser.add_argument("--signin-log", required=True)
domain_parser = subparsers.add_parser("analyze-domain", help="Check domain for AiTM")
domain_parser.add_argument("--domain", required=True)
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
if args.command == "detect":
with open(args.signin_log) as f:
signins = json.load(f)
result = detect_aitm_signins(signins)
if args.json:
print(json.dumps(asdict(result), indent=2, default=str))
else:
print(f"Total sign-ins: {result.total_signins}")
print(f"Suspicious: {result.suspicious_signins}")
print(f"Session replays: {result.session_replays_detected}")
print(f"Impossible travel: {result.impossible_travel_detected}")
print(f"Affected users: {len(result.affected_users)}")
for ind in result.indicators:
print(f" [{ind.severity.upper()}] {ind.description} (user: {ind.user})")
elif args.command == "analyze-domain":
result = analyze_domain(args.domain)
print(json.dumps(result, indent=2))
else:
parser.print_help()
if __name__ == "__main__":
main()