mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-14 23:14:55 +03:00
276 lines
10 KiB
Python
276 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""LaZagne credential access detection agent.
|
|
|
|
Detects evidence of LaZagne credential harvesting tool execution on
|
|
endpoints by scanning for process artifacts, file system indicators,
|
|
and Windows event log entries associated with credential dumping.
|
|
Used for defensive detection and incident response, not offensive use.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
LAZAGNE_INDICATORS = {
|
|
"file_paths": [
|
|
"lazagne.exe", "LaZagne.exe", "laZagne.py",
|
|
"lazagne_output.txt", "credentials.txt",
|
|
],
|
|
"process_names": [
|
|
"lazagne.exe", "python.exe lazagne",
|
|
],
|
|
"registry_keys": [
|
|
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
|
|
],
|
|
"event_ids": {
|
|
4688: "Process creation (look for lazagne.exe or suspicious python)",
|
|
4663: "File access audit (credential store access)",
|
|
4656: "Handle request to credential objects",
|
|
},
|
|
"credential_stores": [
|
|
# Windows
|
|
r"%APPDATA%\Mozilla\Firefox\Profiles",
|
|
r"%LOCALAPPDATA%\Google\Chrome\User Data\Default\Login Data",
|
|
r"%APPDATA%\Opera Software\Opera Stable\Login Data",
|
|
# Linux
|
|
os.path.expanduser("~/.mozilla/firefox"),
|
|
os.path.expanduser("~/.config/google-chrome/Default/Login Data"),
|
|
"/etc/shadow",
|
|
os.path.expanduser("~/.ssh"),
|
|
],
|
|
}
|
|
|
|
|
|
def scan_filesystem_indicators(search_paths=None):
|
|
"""Scan file system for LaZagne artifacts."""
|
|
findings = []
|
|
if search_paths is None:
|
|
if sys.platform == "win32":
|
|
search_paths = [
|
|
os.environ.get("TEMP", "C:\\Temp"),
|
|
os.environ.get("USERPROFILE", "C:\\Users\\Default"),
|
|
"C:\\Windows\\Temp",
|
|
]
|
|
else:
|
|
search_paths = ["/tmp", "/var/tmp", os.path.expanduser("~")]
|
|
|
|
print("[*] Scanning filesystem for LaZagne indicators...")
|
|
for search_path in search_paths:
|
|
if not os.path.isdir(search_path):
|
|
continue
|
|
for root, dirs, files in os.walk(search_path):
|
|
for fname in files:
|
|
fname_lower = fname.lower()
|
|
for indicator in LAZAGNE_INDICATORS["file_paths"]:
|
|
if indicator.lower() in fname_lower:
|
|
full_path = os.path.join(root, fname)
|
|
stat = os.stat(full_path)
|
|
findings.append({
|
|
"type": "file_indicator",
|
|
"path": full_path,
|
|
"indicator": indicator,
|
|
"size": stat.st_size,
|
|
"modified": datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat(),
|
|
"severity": "CRITICAL",
|
|
"description": f"LaZagne artifact found: {fname}",
|
|
})
|
|
# Limit directory depth to avoid slow scans
|
|
if root.count(os.sep) - search_path.count(os.sep) > 3:
|
|
dirs.clear()
|
|
|
|
return findings
|
|
|
|
|
|
def check_windows_event_logs():
|
|
"""Check Windows Security event logs for LaZagne-related activity."""
|
|
findings = []
|
|
if sys.platform != "win32":
|
|
return findings
|
|
|
|
print("[*] Checking Windows Security event logs...")
|
|
|
|
# Check for process creation events matching LaZagne
|
|
ps_script = """
|
|
Get-WinEvent -FilterHashtable @{
|
|
LogName='Security'; Id=4688; StartTime=(Get-Date).AddDays(-7)
|
|
} -MaxEvents 1000 -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.Properties[5].Value -match 'lazagne|LaZagne' -or
|
|
$_.Properties[8].Value -match 'lazagne|LaZagne'
|
|
} |
|
|
Select-Object TimeCreated, @{N='ProcessName';E={$_.Properties[5].Value}},
|
|
@{N='CommandLine';E={$_.Properties[8].Value}},
|
|
@{N='User';E={$_.Properties[1].Value}} |
|
|
ConvertTo-Json
|
|
"""
|
|
result = subprocess.run(
|
|
["powershell", "-Command", ps_script],
|
|
capture_output=True, text=True, timeout=60,
|
|
)
|
|
if result.returncode == 0 and result.stdout.strip():
|
|
try:
|
|
events = json.loads(result.stdout)
|
|
if isinstance(events, dict):
|
|
events = [events]
|
|
for evt in events:
|
|
findings.append({
|
|
"type": "event_log",
|
|
"event_id": 4688,
|
|
"time": str(evt.get("TimeCreated", "")),
|
|
"process": evt.get("ProcessName", ""),
|
|
"command_line": evt.get("CommandLine", "")[:200],
|
|
"user": evt.get("User", ""),
|
|
"severity": "CRITICAL",
|
|
"description": "LaZagne process execution detected in event logs",
|
|
})
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
# Check for credential file access (Event ID 4663)
|
|
ps_script2 = """
|
|
Get-WinEvent -FilterHashtable @{
|
|
LogName='Security'; Id=4663; StartTime=(Get-Date).AddDays(-7)
|
|
} -MaxEvents 500 -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.Properties[6].Value -match 'Login Data|logins.json|Credentials|vault'
|
|
} |
|
|
Select-Object TimeCreated, @{N='ObjectName';E={$_.Properties[6].Value}},
|
|
@{N='ProcessName';E={$_.Properties[11].Value}} |
|
|
ConvertTo-Json
|
|
"""
|
|
result = subprocess.run(
|
|
["powershell", "-Command", ps_script2],
|
|
capture_output=True, text=True, timeout=60,
|
|
)
|
|
if result.returncode == 0 and result.stdout.strip():
|
|
try:
|
|
events = json.loads(result.stdout)
|
|
if isinstance(events, dict):
|
|
events = [events]
|
|
for evt in events:
|
|
findings.append({
|
|
"type": "credential_access",
|
|
"event_id": 4663,
|
|
"time": str(evt.get("TimeCreated", "")),
|
|
"object": evt.get("ObjectName", ""),
|
|
"process": evt.get("ProcessName", ""),
|
|
"severity": "HIGH",
|
|
"description": "Credential store accessed by process",
|
|
})
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
return findings
|
|
|
|
|
|
def check_credential_store_integrity():
|
|
"""Check if credential stores have been recently accessed unusually."""
|
|
findings = []
|
|
print("[*] Checking credential store integrity...")
|
|
|
|
stores_to_check = []
|
|
if sys.platform == "win32":
|
|
appdata = os.environ.get("APPDATA", "")
|
|
localappdata = os.environ.get("LOCALAPPDATA", "")
|
|
stores_to_check = [
|
|
(os.path.join(localappdata, "Google", "Chrome", "User Data", "Default", "Login Data"), "Chrome"),
|
|
(os.path.join(appdata, "Mozilla", "Firefox"), "Firefox"),
|
|
]
|
|
else:
|
|
stores_to_check = [
|
|
(os.path.expanduser("~/.config/google-chrome/Default/Login Data"), "Chrome"),
|
|
(os.path.expanduser("~/.mozilla/firefox"), "Firefox"),
|
|
(os.path.expanduser("~/.ssh"), "SSH Keys"),
|
|
]
|
|
|
|
for path, store_name in stores_to_check:
|
|
if os.path.exists(path):
|
|
if os.path.isfile(path):
|
|
stat = os.stat(path)
|
|
access_time = datetime.fromtimestamp(stat.st_atime, tz=timezone.utc)
|
|
mod_time = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)
|
|
findings.append({
|
|
"type": "credential_store",
|
|
"store": store_name,
|
|
"path": path,
|
|
"last_accessed": access_time.isoformat(),
|
|
"last_modified": mod_time.isoformat(),
|
|
"severity": "INFO",
|
|
})
|
|
|
|
return findings
|
|
|
|
|
|
def format_summary(all_findings):
|
|
"""Print detection summary."""
|
|
print(f"\n{'='*60}")
|
|
print(f" LaZagne Credential Access Detection Report")
|
|
print(f"{'='*60}")
|
|
|
|
file_findings = [f for f in all_findings if f["type"] == "file_indicator"]
|
|
event_findings = [f for f in all_findings if f["type"] in ("event_log", "credential_access")]
|
|
store_findings = [f for f in all_findings if f["type"] == "credential_store"]
|
|
|
|
print(f" File Indicators : {len(file_findings)}")
|
|
print(f" Event Log Hits : {len(event_findings)}")
|
|
print(f" Credential Stores: {len(store_findings)}")
|
|
|
|
critical = [f for f in all_findings if f.get("severity") == "CRITICAL"]
|
|
if critical:
|
|
print(f"\n CRITICAL FINDINGS ({len(critical)}):")
|
|
for f in critical:
|
|
print(f" [{f['type']}] {f.get('description', f.get('path', ''))}")
|
|
|
|
severity_counts = {}
|
|
for f in all_findings:
|
|
sev = f.get("severity", "INFO")
|
|
severity_counts[sev] = severity_counts.get(sev, 0) + 1
|
|
return severity_counts
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="LaZagne credential access detection agent (defensive use)"
|
|
)
|
|
parser.add_argument("--search-paths", nargs="+", help="Paths to scan for artifacts")
|
|
parser.add_argument("--skip-events", action="store_true", help="Skip Windows event log check")
|
|
parser.add_argument("--skip-stores", action="store_true", help="Skip credential store check")
|
|
parser.add_argument("--output", "-o", help="Output JSON report")
|
|
parser.add_argument("--verbose", "-v", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
all_findings = []
|
|
all_findings.extend(scan_filesystem_indicators(args.search_paths))
|
|
if not args.skip_events:
|
|
all_findings.extend(check_windows_event_logs())
|
|
if not args.skip_stores:
|
|
all_findings.extend(check_credential_store_integrity())
|
|
|
|
severity_counts = format_summary(all_findings)
|
|
|
|
report = {
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
"tool": "LaZagne Detection",
|
|
"findings": all_findings,
|
|
"severity_counts": severity_counts,
|
|
"risk_level": (
|
|
"CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
|
|
else "HIGH" if severity_counts.get("HIGH", 0) > 0
|
|
else "LOW"
|
|
),
|
|
}
|
|
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
print(f"\n[+] Report saved to {args.output}")
|
|
elif args.verbose:
|
|
print(json.dumps(report, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|