mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 22:24:56 +03:00
285 lines
11 KiB
Python
285 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Threat Actor Infrastructure Tracking Script
|
|
|
|
Tracks and maps adversary infrastructure using:
|
|
- Shodan/Censys for service discovery
|
|
- Passive DNS for domain-IP relationships
|
|
- Certificate Transparency for certificate monitoring
|
|
- WHOIS for registration data pivoting
|
|
|
|
Requirements:
|
|
pip install shodan requests stix2
|
|
|
|
Usage:
|
|
python process.py --ip 198.51.100.1 --shodan-key KEY
|
|
python process.py --domain evil.com --ct-search
|
|
python process.py --c2-hunt cobalt-strike --shodan-key KEY
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from datetime import datetime
|
|
from collections import defaultdict
|
|
from typing import Optional
|
|
|
|
import requests
|
|
|
|
try:
|
|
import shodan
|
|
except ImportError:
|
|
shodan = None
|
|
|
|
|
|
class InfrastructureTracker:
|
|
"""Track threat actor infrastructure across multiple data sources."""
|
|
|
|
def __init__(self, shodan_key: str = "", securitytrails_key: str = ""):
|
|
self.shodan_api = shodan.Shodan(shodan_key) if shodan and shodan_key else None
|
|
self.st_key = securitytrails_key
|
|
self.findings = {"ips": {}, "domains": {}, "certificates": [], "pivots": []}
|
|
|
|
def shodan_host_lookup(self, ip: str) -> Optional[dict]:
|
|
"""Look up IP on Shodan."""
|
|
if not self.shodan_api:
|
|
print("[-] Shodan API not configured")
|
|
return None
|
|
try:
|
|
host = self.shodan_api.host(ip)
|
|
result = {
|
|
"ip": ip,
|
|
"org": host.get("org", ""),
|
|
"asn": host.get("asn", ""),
|
|
"isp": host.get("isp", ""),
|
|
"country": host.get("country_name", ""),
|
|
"city": host.get("city", ""),
|
|
"os": host.get("os"),
|
|
"ports": host.get("ports", []),
|
|
"vulns": host.get("vulns", []),
|
|
"hostnames": host.get("hostnames", []),
|
|
"services": [],
|
|
}
|
|
for svc in host.get("data", []):
|
|
service = {
|
|
"port": svc.get("port"),
|
|
"transport": svc.get("transport"),
|
|
"product": svc.get("product", ""),
|
|
"version": svc.get("version", ""),
|
|
"banner": svc.get("data", "")[:200],
|
|
}
|
|
ssl = svc.get("ssl", {})
|
|
if ssl:
|
|
service["jarm"] = ssl.get("jarm", "")
|
|
service["ja3s"] = ssl.get("ja3s", "")
|
|
cert = ssl.get("cert", {})
|
|
if cert:
|
|
service["cert_subject"] = cert.get("subject", {})
|
|
service["cert_issuer"] = cert.get("issuer", {})
|
|
service["cert_expires"] = cert.get("expires", "")
|
|
result["services"].append(service)
|
|
|
|
self.findings["ips"][ip] = result
|
|
print(f"[+] Shodan: {ip} - {result['org']} - Ports: {result['ports']}")
|
|
return result
|
|
except Exception as e:
|
|
print(f"[-] Shodan error for {ip}: {e}")
|
|
return None
|
|
|
|
def search_c2_servers(self, framework: str, limit: int = 50) -> list:
|
|
"""Search for C2 framework servers on Shodan."""
|
|
if not self.shodan_api:
|
|
return []
|
|
|
|
queries = {
|
|
"cobalt-strike": 'product:"Cobalt Strike Beacon"',
|
|
"metasploit": 'product:"Metasploit"',
|
|
"sliver": 'ssl:"multiplayer" ssl:"operators"',
|
|
"havoc": 'http.html_hash:-1472705893',
|
|
"brute-ratel": 'http.html_hash:"-1957161625"',
|
|
}
|
|
|
|
query = queries.get(framework.lower(), framework)
|
|
try:
|
|
results = self.shodan_api.search(query, limit=limit)
|
|
servers = []
|
|
for match in results.get("matches", []):
|
|
servers.append({
|
|
"ip": match["ip_str"],
|
|
"port": match["port"],
|
|
"org": match.get("org", ""),
|
|
"asn": match.get("asn", ""),
|
|
"country": match.get("location", {}).get("country_name", ""),
|
|
"timestamp": match.get("timestamp", ""),
|
|
})
|
|
print(f"[+] Found {len(servers)} {framework} servers")
|
|
return servers
|
|
except Exception as e:
|
|
print(f"[-] C2 search error: {e}")
|
|
return []
|
|
|
|
def ct_log_search(self, domain: str) -> Optional[dict]:
|
|
"""Search Certificate Transparency logs via crt.sh."""
|
|
try:
|
|
resp = requests.get(
|
|
f"https://crt.sh/?q=%.{domain}&output=json", timeout=30
|
|
)
|
|
if resp.status_code == 200:
|
|
certs = resp.json()
|
|
unique_domains = set()
|
|
for cert in certs:
|
|
for name in cert.get("name_value", "").split("\n"):
|
|
name = name.strip()
|
|
if name:
|
|
unique_domains.add(name)
|
|
|
|
result = {
|
|
"domain": domain,
|
|
"total_certs": len(certs),
|
|
"unique_domains": sorted(unique_domains),
|
|
"recent_certs": [
|
|
{
|
|
"common_name": c.get("common_name", ""),
|
|
"issuer": c.get("issuer_name", ""),
|
|
"not_before": c.get("not_before", ""),
|
|
"not_after": c.get("not_after", ""),
|
|
}
|
|
for c in certs[:20]
|
|
],
|
|
}
|
|
self.findings["certificates"].append(result)
|
|
print(f"[+] CT: {domain} - {len(certs)} certs, {len(unique_domains)} domains")
|
|
return result
|
|
except Exception as e:
|
|
print(f"[-] CT search error: {e}")
|
|
return None
|
|
|
|
def passive_dns_securitytrails(self, domain: str) -> Optional[dict]:
|
|
"""Query SecurityTrails passive DNS."""
|
|
if not self.st_key:
|
|
print("[-] SecurityTrails API key not configured")
|
|
return None
|
|
try:
|
|
resp = requests.get(
|
|
f"https://api.securitytrails.com/v1/domain/{domain}",
|
|
headers={"APIKEY": self.st_key},
|
|
timeout=30,
|
|
)
|
|
if resp.status_code == 200:
|
|
data = resp.json()
|
|
dns = data.get("current_dns", {})
|
|
result = {
|
|
"domain": domain,
|
|
"a_records": [
|
|
r.get("ip") for r in dns.get("a", {}).get("values", [])
|
|
],
|
|
"mx_records": [
|
|
r.get("host") for r in dns.get("mx", {}).get("values", [])
|
|
],
|
|
"ns_records": [
|
|
r.get("nameserver") for r in dns.get("ns", {}).get("values", [])
|
|
],
|
|
"alexa_rank": data.get("alexa_rank"),
|
|
}
|
|
self.findings["domains"][domain] = result
|
|
print(f"[+] pDNS: {domain} -> {result['a_records']}")
|
|
return result
|
|
except Exception as e:
|
|
print(f"[-] SecurityTrails error: {e}")
|
|
return None
|
|
|
|
def pivot_from_ip(self, ip: str) -> dict:
|
|
"""Perform full infrastructure pivot from an IP address."""
|
|
pivot_results = {"origin_ip": ip, "discovered": []}
|
|
|
|
# Shodan lookup
|
|
shodan_data = self.shodan_host_lookup(ip)
|
|
if shodan_data:
|
|
for hostname in shodan_data.get("hostnames", []):
|
|
pivot_results["discovered"].append({
|
|
"type": "domain",
|
|
"value": hostname,
|
|
"source": "shodan_hostname",
|
|
})
|
|
|
|
for svc in shodan_data.get("services", []):
|
|
cert_cn = svc.get("cert_subject", {}).get("CN", "")
|
|
if cert_cn and cert_cn != ip:
|
|
pivot_results["discovered"].append({
|
|
"type": "domain",
|
|
"value": cert_cn,
|
|
"source": "ssl_certificate",
|
|
})
|
|
|
|
# CT search for discovered domains
|
|
seen_domains = set()
|
|
for item in pivot_results["discovered"]:
|
|
if item["type"] == "domain":
|
|
domain = item["value"]
|
|
if domain not in seen_domains:
|
|
seen_domains.add(domain)
|
|
ct = self.ct_log_search(domain)
|
|
if ct:
|
|
for d in ct.get("unique_domains", []):
|
|
if d not in seen_domains:
|
|
pivot_results["discovered"].append({
|
|
"type": "domain",
|
|
"value": d,
|
|
"source": "ct_log",
|
|
})
|
|
|
|
self.findings["pivots"].append(pivot_results)
|
|
return pivot_results
|
|
|
|
def generate_report(self) -> dict:
|
|
"""Generate infrastructure tracking report."""
|
|
return {
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"summary": {
|
|
"ips_tracked": len(self.findings["ips"]),
|
|
"domains_tracked": len(self.findings["domains"]),
|
|
"certificates_found": sum(
|
|
c.get("total_certs", 0) for c in self.findings["certificates"]
|
|
),
|
|
"pivots_performed": len(self.findings["pivots"]),
|
|
},
|
|
"findings": self.findings,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Infrastructure Tracking Tool")
|
|
parser.add_argument("--ip", help="IP address to investigate")
|
|
parser.add_argument("--domain", help="Domain to investigate")
|
|
parser.add_argument("--c2-hunt", help="C2 framework to hunt")
|
|
parser.add_argument("--ct-search", action="store_true", help="Search CT logs")
|
|
parser.add_argument("--pivot", action="store_true", help="Full pivot from IP")
|
|
parser.add_argument("--shodan-key", default="", help="Shodan API key")
|
|
parser.add_argument("--st-key", default="", help="SecurityTrails API key")
|
|
parser.add_argument("--output", default="infra_report.json", help="Output file")
|
|
|
|
args = parser.parse_args()
|
|
tracker = InfrastructureTracker(args.shodan_key, args.st_key)
|
|
|
|
if args.ip and args.pivot:
|
|
results = tracker.pivot_from_ip(args.ip)
|
|
print(json.dumps(results, indent=2))
|
|
elif args.ip:
|
|
tracker.shodan_host_lookup(args.ip)
|
|
elif args.domain and args.ct_search:
|
|
tracker.ct_log_search(args.domain)
|
|
elif args.domain:
|
|
tracker.passive_dns_securitytrails(args.domain)
|
|
elif args.c2_hunt:
|
|
servers = tracker.search_c2_servers(args.c2_hunt)
|
|
print(json.dumps(servers, indent=2))
|
|
|
|
report = tracker.generate_report()
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
print(f"[+] Report saved to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|