mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
255 lines
9.7 KiB
Python
255 lines
9.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Browser forensics analysis agent using Hindsight concepts.
|
|
|
|
Parses Chromium-based browser artifacts (Chrome, Edge, Brave) including
|
|
history, downloads, cookies, autofill, and extensions from SQLite databases.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import sqlite3
|
|
import datetime
|
|
|
|
|
|
def chrome_time_to_datetime(chrome_time):
|
|
"""Convert Chrome timestamp (microseconds since 1601-01-01) to datetime."""
|
|
if not chrome_time or chrome_time == 0:
|
|
return None
|
|
try:
|
|
epoch = datetime.datetime(1601, 1, 1)
|
|
delta = datetime.timedelta(microseconds=chrome_time)
|
|
return (epoch + delta).isoformat() + "Z"
|
|
except (OverflowError, OSError):
|
|
return None
|
|
|
|
|
|
def find_browser_profiles(base_path=None):
|
|
"""Locate Chromium-based browser profile directories."""
|
|
if base_path and os.path.isdir(base_path):
|
|
return [base_path]
|
|
profiles = []
|
|
home = os.path.expanduser("~")
|
|
candidates = [
|
|
os.path.join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default"),
|
|
os.path.join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default"),
|
|
os.path.join(home, "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data", "Default"),
|
|
os.path.join(home, ".config", "google-chrome", "Default"),
|
|
os.path.join(home, ".config", "chromium", "Default"),
|
|
os.path.join(home, ".config", "microsoft-edge", "Default"),
|
|
]
|
|
for path in candidates:
|
|
if os.path.isdir(path):
|
|
profiles.append(path)
|
|
return profiles
|
|
|
|
|
|
def parse_history(profile_path):
|
|
"""Parse browsing history from History SQLite database."""
|
|
db_path = os.path.join(profile_path, "History")
|
|
if not os.path.exists(db_path):
|
|
return []
|
|
entries = []
|
|
try:
|
|
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
|
cursor = conn.cursor()
|
|
cursor.execute("""
|
|
SELECT u.url, u.title, v.visit_time, v.transition, u.visit_count
|
|
FROM visits v JOIN urls u ON v.url = u.id
|
|
ORDER BY v.visit_time DESC LIMIT 5000
|
|
""")
|
|
for url, title, visit_time, transition, count in cursor.fetchall():
|
|
entries.append({
|
|
"url": url, "title": title or "",
|
|
"visit_time": chrome_time_to_datetime(visit_time),
|
|
"transition": transition & 0xFF,
|
|
"visit_count": count,
|
|
})
|
|
conn.close()
|
|
except sqlite3.Error as e:
|
|
entries.append({"error": str(e)})
|
|
return entries
|
|
|
|
|
|
def parse_downloads(profile_path):
|
|
"""Parse download history from History database."""
|
|
db_path = os.path.join(profile_path, "History")
|
|
if not os.path.exists(db_path):
|
|
return []
|
|
downloads = []
|
|
try:
|
|
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
|
cursor = conn.cursor()
|
|
cursor.execute("""
|
|
SELECT target_path, tab_url, total_bytes, start_time, end_time,
|
|
danger_type, interrupt_reason, mime_type
|
|
FROM downloads ORDER BY start_time DESC LIMIT 1000
|
|
""")
|
|
for row in cursor.fetchall():
|
|
downloads.append({
|
|
"target_path": row[0], "source_url": row[1],
|
|
"total_bytes": row[2],
|
|
"start_time": chrome_time_to_datetime(row[3]),
|
|
"end_time": chrome_time_to_datetime(row[4]),
|
|
"danger_type": row[5], "interrupt_reason": row[6],
|
|
"mime_type": row[7],
|
|
})
|
|
conn.close()
|
|
except sqlite3.Error as e:
|
|
downloads.append({"error": str(e)})
|
|
return downloads
|
|
|
|
|
|
def parse_cookies(profile_path):
|
|
"""Parse cookies from Cookies database."""
|
|
db_path = os.path.join(profile_path, "Cookies")
|
|
if not os.path.exists(db_path):
|
|
db_path = os.path.join(profile_path, "Network", "Cookies")
|
|
if not os.path.exists(db_path):
|
|
return []
|
|
cookies = []
|
|
try:
|
|
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
|
cursor = conn.cursor()
|
|
cursor.execute("""
|
|
SELECT host_key, name, path, creation_utc, expires_utc,
|
|
is_secure, is_httponly, samesite
|
|
FROM cookies ORDER BY creation_utc DESC LIMIT 2000
|
|
""")
|
|
for row in cursor.fetchall():
|
|
cookies.append({
|
|
"host": row[0], "name": row[1], "path": row[2],
|
|
"created": chrome_time_to_datetime(row[3]),
|
|
"expires": chrome_time_to_datetime(row[4]),
|
|
"secure": bool(row[5]), "httponly": bool(row[6]),
|
|
"samesite": row[7],
|
|
})
|
|
conn.close()
|
|
except sqlite3.Error as e:
|
|
cookies.append({"error": str(e)})
|
|
return cookies
|
|
|
|
|
|
def parse_autofill(profile_path):
|
|
"""Parse autofill data from Web Data database."""
|
|
db_path = os.path.join(profile_path, "Web Data")
|
|
if not os.path.exists(db_path):
|
|
return []
|
|
entries = []
|
|
try:
|
|
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
|
cursor = conn.cursor()
|
|
cursor.execute("""
|
|
SELECT name, value, count, date_created, date_last_used
|
|
FROM autofill ORDER BY date_last_used DESC LIMIT 500
|
|
""")
|
|
for row in cursor.fetchall():
|
|
entries.append({
|
|
"field_name": row[0], "value": row[1][:50] + "..." if len(row[1]) > 50 else row[1],
|
|
"usage_count": row[2],
|
|
"created": chrome_time_to_datetime(row[3] * 1000000 if row[3] else 0),
|
|
"last_used": chrome_time_to_datetime(row[4] * 1000000 if row[4] else 0),
|
|
})
|
|
conn.close()
|
|
except sqlite3.Error as e:
|
|
entries.append({"error": str(e)})
|
|
return entries
|
|
|
|
|
|
def parse_extensions(profile_path):
|
|
"""Parse installed browser extensions."""
|
|
ext_dir = os.path.join(profile_path, "Extensions")
|
|
extensions = []
|
|
if not os.path.isdir(ext_dir):
|
|
return extensions
|
|
for ext_id in os.listdir(ext_dir):
|
|
ext_path = os.path.join(ext_dir, ext_id)
|
|
if os.path.isdir(ext_path):
|
|
versions = sorted(os.listdir(ext_path))
|
|
manifest_path = os.path.join(ext_path, versions[-1], "manifest.json") if versions else None
|
|
name = ext_id
|
|
if manifest_path and os.path.exists(manifest_path):
|
|
try:
|
|
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
manifest = json.load(f)
|
|
name = manifest.get("name", ext_id)
|
|
extensions.append({
|
|
"id": ext_id, "name": name,
|
|
"version": manifest.get("version", "?"),
|
|
"permissions": manifest.get("permissions", [])[:10],
|
|
})
|
|
except (json.JSONDecodeError, IOError):
|
|
extensions.append({"id": ext_id, "name": name, "version": "unknown"})
|
|
return extensions
|
|
|
|
|
|
def detect_suspicious_activity(history, downloads):
|
|
"""Flag suspicious browsing and download patterns."""
|
|
findings = []
|
|
suspicious_domains = ["pastebin.com", "ngrok.io", "raw.githubusercontent.com",
|
|
"transfer.sh", "file.io", "temp.sh", "anonfiles.com"]
|
|
for entry in history:
|
|
url = entry.get("url", "").lower()
|
|
for domain in suspicious_domains:
|
|
if domain in url:
|
|
findings.append({
|
|
"type": "suspicious_url", "url": entry["url"],
|
|
"domain": domain, "time": entry.get("visit_time"),
|
|
})
|
|
dangerous_mimes = ["application/x-msdownload", "application/x-msdos-program",
|
|
"application/x-executable", "application/vnd.ms-excel.sheet.macroEnabled"]
|
|
for dl in downloads:
|
|
if dl.get("danger_type", 0) > 0:
|
|
findings.append({
|
|
"type": "dangerous_download", "path": dl.get("target_path"),
|
|
"source": dl.get("source_url"), "danger_type": dl.get("danger_type"),
|
|
})
|
|
if dl.get("mime_type", "") in dangerous_mimes:
|
|
findings.append({
|
|
"type": "suspicious_mime", "mime": dl.get("mime_type"),
|
|
"path": dl.get("target_path"),
|
|
})
|
|
return findings
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("=" * 60)
|
|
print("Browser Forensics Analysis Agent")
|
|
print("Chromium history, downloads, cookies, extensions")
|
|
print("=" * 60)
|
|
|
|
target = sys.argv[1] if len(sys.argv) > 1 else None
|
|
profiles = find_browser_profiles(target)
|
|
|
|
if not profiles:
|
|
print("\n[!] No browser profiles found.")
|
|
print("[DEMO] Usage: python agent.py <profile_path>")
|
|
print(" e.g. python agent.py ~/AppData/Local/Google/Chrome/User\\ Data/Default")
|
|
sys.exit(0)
|
|
|
|
for profile in profiles:
|
|
print(f"\n[*] Profile: {profile}")
|
|
|
|
history = parse_history(profile)
|
|
print(f" History entries: {len(history)}")
|
|
for h in history[:5]:
|
|
print(f" {h.get('visit_time', '?')} | {h.get('title', '')[:50]} | {h.get('url', '')[:60]}")
|
|
|
|
downloads = parse_downloads(profile)
|
|
print(f" Downloads: {len(downloads)}")
|
|
for d in downloads[:5]:
|
|
print(f" {d.get('start_time', '?')} | {d.get('mime_type', '?')} | {os.path.basename(d.get('target_path', ''))}")
|
|
|
|
cookies = parse_cookies(profile)
|
|
print(f" Cookies: {len(cookies)}")
|
|
|
|
extensions = parse_extensions(profile)
|
|
print(f" Extensions: {len(extensions)}")
|
|
for ext in extensions[:5]:
|
|
print(f" {ext.get('name', '?')} v{ext.get('version', '?')} [{ext.get('id', '')[:20]}]")
|
|
|
|
findings = detect_suspicious_activity(history, downloads)
|
|
print(f"\n --- Suspicious Activity: {len(findings)} findings ---")
|
|
for f in findings[:10]:
|
|
print(f" [{f['type']}] {f.get('url', f.get('path', ''))}")
|