threat intelligence

Tracking Threat Actor Infrastructure

Threat actor infrastructure tracking involves monitoring and mapping adversary-controlled assets including command-and-control (C2) servers, phishing domains, exploit kit hosts, bulletproof hosting, and staging servers. This skill covers using passive DNS, certificate transparency logs, Shodan/Censys scanning, WHOIS analysis, and network fingerprinting to discover, track, and pivot across threat actor infrastructure over time.

censysctiinfrastructure-trackingiocmitre-attackpassive-dnsshodanstix
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Threat actor infrastructure tracking involves monitoring and mapping adversary-controlled assets including command-and-control (C2) servers, phishing domains, exploit kit hosts, bulletproof hosting, and staging servers. This skill covers using passive DNS, certificate transparency logs, Shodan/Censys scanning, WHOIS analysis, and network fingerprinting to discover, track, and pivot across threat actor infrastructure over time.

When to Use

  • When managing security operations that require tracking threat actor infrastructure
  • When improving security program maturity and operational processes
  • When establishing standardized procedures for security team workflows
  • When integrating threat intelligence or vulnerability data into operations

Prerequisites

  • Python 3.9+ with shodan, censys, requests, stix2 libraries
  • API keys: Shodan, Censys, VirusTotal, SecurityTrails, PassiveTotal
  • Understanding of DNS, TLS/SSL certificates, IP allocation, ASN structure
  • Familiarity with passive DNS and certificate transparency concepts
  • Access to domain registration (WHOIS) lookup services

Key Concepts

Infrastructure Pivoting

Pivoting is the technique of using one known indicator to discover related infrastructure. Starting from a known C2 IP address, analysts can pivot via: passive DNS (find domains), reverse WHOIS (find related registrations), SSL certificates (find shared certs), SSH key fingerprints, HTTP response fingerprints, JARM/JA3S hashes, and WHOIS registrant data.

Passive DNS

Passive DNS databases record DNS query/response data observed at recursive resolvers. This allows analysts to find historical domain-to-IP mappings, discover domains hosted on a known C2 IP, and identify fast-flux or domain generation algorithm (DGA) behavior.

Certificate Transparency

Certificate Transparency (CT) logs publicly record all SSL/TLS certificates issued by CAs. Monitoring CT logs reveals new certificates registered for suspicious domains, helping identify phishing sites and C2 infrastructure before they become active.

Network Fingerprinting

  • JARM: Active TLS server fingerprint (hash of TLS handshake responses)
  • JA3S: Passive TLS server fingerprint (hash of Server Hello)
  • HTTP Headers: Server banners, custom headers, response patterns
  • Favicon Hash: Hash of HTTP favicon for server identification

Workflow

Step 1: Shodan Infrastructure Discovery

import shodan
 
api = shodan.Shodan("YOUR_SHODAN_API_KEY")
 
def discover_infrastructure(ip_address):
    """Discover services and metadata for a target IP."""
    try:
        host = api.host(ip_address)
        return {
            "ip": host["ip_str"],
            "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", []),
            "domains": host.get("domains", []),
            "tags": host.get("tags", []),
            "services": [
                {
                    "port": svc.get("port"),
                    "transport": svc.get("transport"),
                    "product": svc.get("product", ""),
                    "version": svc.get("version", ""),
                    "ssl_cert": svc.get("ssl", {}).get("cert", {}).get("subject", {}),
                    "jarm": svc.get("ssl", {}).get("jarm", ""),
                }
                for svc in host.get("data", [])
            ],
        }
    except shodan.APIError as e:
        print(f"[-] Shodan error: {e}")
        return None
 
def search_c2_framework(framework_name):
    """Search Shodan for known C2 framework signatures."""
    c2_queries = {
        "cobalt-strike": 'product:"Cobalt Strike Beacon"',
        "metasploit": 'product:"Metasploit"',
        "covenant": 'http.html:"Covenant" http.title:"Covenant"',
        "sliver": 'ssl.cert.subject.cn:"multiplayer" ssl.cert.issuer.cn:"operators"',
        "havoc": 'http.html_hash:-1472705893',
    }
 
    query = c2_queries.get(framework_name.lower(), framework_name)
    results = api.search(query, limit=100)
 
    hosts = []
    for match in results.get("matches", []):
        hosts.append({
            "ip": match["ip_str"],
            "port": match["port"],
            "org": match.get("org", ""),
            "country": match.get("location", {}).get("country_name", ""),
            "asn": match.get("asn", ""),
            "timestamp": match.get("timestamp", ""),
        })
 
    return hosts

Step 2: Passive DNS Pivoting

import requests
 
def passive_dns_lookup(indicator, api_key, indicator_type="ip"):
    """Query SecurityTrails for passive DNS records."""
    base_url = "https://api.securitytrails.com/v1"
    headers = {"APIKEY": api_key, "Accept": "application/json"}
 
    if indicator_type == "ip":
        url = f"{base_url}/search/list"
        payload = {
            "filter": {"ipv4": indicator}
        }
        resp = requests.post(url, json=payload, headers=headers, timeout=30)
    else:
        url = f"{base_url}/domain/{indicator}/subdomains"
        resp = requests.get(url, headers=headers, timeout=30)
 
    if resp.status_code == 200:
        return resp.json()
    return None
 
 
def query_passive_total(indicator, user, api_key):
    """Query PassiveTotal for passive DNS and WHOIS data."""
    base_url = "https://api.passivetotal.org/v2"
    auth = (user, api_key)
 
    # Passive DNS
    pdns_resp = requests.get(
        f"{base_url}/dns/passive",
        params={"query": indicator},
        auth=auth,
        timeout=30,
    )
 
    # WHOIS
    whois_resp = requests.get(
        f"{base_url}/whois",
        params={"query": indicator},
        auth=auth,
        timeout=30,
    )
 
    results = {}
    if pdns_resp.status_code == 200:
        results["passive_dns"] = pdns_resp.json().get("results", [])
    if whois_resp.status_code == 200:
        results["whois"] = whois_resp.json()
 
    return results

Step 3: Certificate Transparency Monitoring

import requests
 
def search_ct_logs(domain):
    """Search Certificate Transparency logs via crt.sh."""
    resp = requests.get(
        f"https://crt.sh/?q=%.{domain}&output=json",
        timeout=30,
    )
 
    if resp.status_code == 200:
        certs = resp.json()
        unique_domains = set()
        cert_info = []
 
        for cert in certs:
            name_value = cert.get("name_value", "")
            for name in name_value.split("\n"):
                unique_domains.add(name.strip())
 
            cert_info.append({
                "id": cert.get("id"),
                "issuer": cert.get("issuer_name", ""),
                "common_name": cert.get("common_name", ""),
                "name_value": name_value,
                "not_before": cert.get("not_before", ""),
                "not_after": cert.get("not_after", ""),
                "serial_number": cert.get("serial_number", ""),
            })
 
        return {
            "domain": domain,
            "total_certificates": len(certs),
            "unique_domains": sorted(unique_domains),
            "certificates": cert_info[:50],
        }
    return None
 
 
def monitor_new_certs(domains, interval_hours=1):
    """Monitor for newly issued certificates for a list of domains."""
    from datetime import datetime, timedelta
 
    cutoff = (datetime.utcnow() - timedelta(hours=interval_hours)).isoformat()
    new_certs = []
 
    for domain in domains:
        result = search_ct_logs(domain)
        if result:
            for cert in result.get("certificates", []):
                if cert.get("not_before", "") > cutoff:
                    new_certs.append({
                        "domain": domain,
                        "cert": cert,
                    })
 
    return new_certs

Step 4: Infrastructure Correlation and Timeline

from datetime import datetime
 
def build_infrastructure_timeline(indicators):
    """Build a timeline of infrastructure changes."""
    timeline = []
 
    for ind in indicators:
        if "passive_dns" in ind:
            for record in ind["passive_dns"]:
                timeline.append({
                    "timestamp": record.get("firstSeen", ""),
                    "event": "dns_resolution",
                    "source": record.get("resolve", ""),
                    "target": record.get("value", ""),
                    "record_type": record.get("recordType", ""),
                })
 
        if "certificates" in ind:
            for cert in ind["certificates"]:
                timeline.append({
                    "timestamp": cert.get("not_before", ""),
                    "event": "certificate_issued",
                    "domain": cert.get("common_name", ""),
                    "issuer": cert.get("issuer", ""),
                })
 
    timeline.sort(key=lambda x: x.get("timestamp", ""))
    return timeline

Validation Criteria

  • Shodan/Censys queries return infrastructure details for target IPs
  • Passive DNS reveals historical domain-IP mappings
  • Certificate transparency search finds associated domains
  • Infrastructure pivoting discovers new related indicators
  • Timeline shows infrastructure evolution over time
  • Results are exportable as STIX 2.1 Infrastructure objects

References

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.8 KB

API Reference: Tracking Threat Actor Infrastructure

Pivoting Techniques

Technique Source Discovers
Passive DNS DNS resolvers Domains on same IP, historical mappings
Reverse WHOIS Registrar data Domains by same registrant
SSL Certificate CT logs, direct Shared certs, SANs, issuers
Shodan/Censys Internet scanning Open ports, services, banners
HTTP fingerprint Server responses Body hash, headers, favicon
JARM/JA3S TLS handshake C2 framework identification

API Endpoints

Service Endpoint Auth
Shodan Host GET /shodan/host/{ip}?key= API key
VirusTotal IP GET /api/v3/ip-addresses/{ip} x-apikey header
VirusTotal Domain GET /api/v3/domains/{domain} x-apikey header
SecurityTrails GET /v1/domain/{d}/subdomains APIKEY header
RDAP WHOIS GET https://rdap.org/domain/{d} None

Network Fingerprinting

Method Tool Description
JARM jarm.py Active TLS server fingerprint
JA3S Zeek/Wireshark Passive TLS Server Hello hash
Favicon hash Shodan http.favicon.hash mmh3 hash of favicon.ico
HTTP body hash SHA-256 Response body fingerprint
Server banner HTTP Server header Software identification

Python Libraries

Library Version Purpose
requests >=2.28 API queries to Shodan/VT
ssl stdlib TLS certificate retrieval
socket stdlib DNS resolution, connections
hashlib stdlib Certificate/content fingerprinting

References

standards.md1.6 KB

Standards and Frameworks Reference

STIX 2.1 Infrastructure Object

{
  "type": "infrastructure",
  "name": "C2 Server",
  "infrastructure_types": ["command-and-control"],
  "description": "Cobalt Strike TeamServer at 198.51.100.1",
  "first_seen": "2025-01-01T00:00:00Z",
  "last_seen": "2025-06-01T00:00:00Z"
}

Diamond Model of Intrusion Analysis

  • Adversary: Threat actor or group
  • Capability: Tools, techniques, and malware
  • Infrastructure: C2 servers, domains, hosting
  • Victim: Targeted organization or individual

Infrastructure Types (STIX vocabulary)

  • command-and-control, botnet, exfiltration, hosting-malware
  • hosting-target-lists, phishing, staging, undefined

Network Fingerprinting Methods

Method Type Description
JARM Active TLS server fingerprint from 10 TLS handshakes
JA3S Passive Server Hello hash from TLS negotiation
JA3 Passive Client Hello hash for client fingerprinting
Favicon Hash Active HTTP favicon file hash
HTTP Headers Active/Passive Server banner and header fingerprinting
SSH Key Active SSH host key fingerprint

Passive DNS Record Types

  • A/AAAA: Domain to IP mapping
  • CNAME: Domain alias
  • MX: Mail server records
  • NS: Nameserver records
  • TXT: Text records (SPF, DKIM, verification)

References

workflows.md2.4 KB

Infrastructure Tracking Workflows

Workflow 1: IP-Centric Pivoting

[Known C2 IP] --> [Shodan/Censys] --> [Service Fingerprints]
      |                                      |
      v                                      v
[Passive DNS] --> [Associated Domains] --> [WHOIS Analysis] --> [Registrant Pivot]
      |                                                               |
      v                                                               v
[SSL Certs] --> [Subject Alt Names] --> [New Domains] --> [Additional IPs]

Workflow 2: Domain-Centric Pivoting

[Known C2 Domain] --> [DNS History] --> [Historical IPs] --> [Co-hosted Domains]
        |                                                          |
        v                                                          v
  [CT Logs] --> [Subdomains] --> [Infrastructure Map] --> [Shared Hosting Analysis]
        |
        v
  [WHOIS] --> [Registrant/Email] --> [Other Registered Domains]

Workflow 3: C2 Framework Hunting

[C2 Signature] --> [Shodan Search] --> [Candidate Servers] --> [Validation]
                                                                    |
                                                                    v
                                                          [JARM Fingerprint]
                                                                    |
                                                                    v
                                                          [Confirm C2 Type]
                                                                    |
                                                                    v
                                                          [Track Over Time]

Workflow 4: Continuous Monitoring

[Watchlist IPs/Domains] --> [Scheduled Scans] --> [Change Detection] --> [Alerts]
                                                         |
                                                +--------+--------+
                                                |        |        |
                                                v        v        v
                                          [New Port] [DNS Change] [New Cert]
                                                |        |        |
                                                v        v        v
                                          [Investigate] [Update TI] [Share]

Scripts 2

agent.py7.6 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for tracking threat actor infrastructure.

Uses passive DNS, certificate transparency, Shodan, WHOIS, and
network fingerprinting to discover, pivot across, and map
adversary-controlled infrastructure.
"""

import json
import sys
import socket
import ssl
import hashlib
from pathlib import Path
from datetime import datetime

try:
    import requests
except ImportError:
    requests = None


class ThreatInfraTracker:
    """Tracks and pivots across threat actor infrastructure."""

    def __init__(self, shodan_key=None, vt_key=None, output_dir="./threat_infra"):
        self.shodan_key = shodan_key
        self.vt_key = vt_key
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.findings = []
        self.infrastructure = {}

    def _get(self, url, params=None, headers=None, timeout=10):
        if not requests:
            return None
        try:
            return requests.get(url, params=params, headers=headers, timeout=timeout)
        except requests.RequestException:
            return None

    def query_shodan(self, ip):
        """Query Shodan for host information and services."""
        if not self.shodan_key:
            return {"error": "No Shodan API key"}
        resp = self._get(f"https://api.shodan.io/shodan/host/{ip}",
                         params={"key": self.shodan_key})
        if resp and resp.status_code == 200:
            data = resp.json()
            result = {
                "ip": ip, "org": data.get("org"), "asn": data.get("asn"),
                "os": data.get("os"), "ports": data.get("ports", []),
                "hostnames": data.get("hostnames", []),
                "vulns": data.get("vulns", []),
                "country": data.get("country_code"),
            }
            self.infrastructure[ip] = result
            return result
        return None

    def query_virustotal(self, indicator, indicator_type="ip"):
        """Query VirusTotal for IP/domain reputation."""
        if not self.vt_key:
            return {"error": "No VT API key"}
        type_map = {"ip": "ip-addresses", "domain": "domains", "hash": "files"}
        endpoint = type_map.get(indicator_type, "ip-addresses")
        resp = self._get(f"https://www.virustotal.com/api/v3/{endpoint}/{indicator}",
                         headers={"x-apikey": self.vt_key})
        if resp and resp.status_code == 200:
            data = resp.json().get("data", {}).get("attributes", {})
            stats = data.get("last_analysis_stats", {})
            result = {
                "indicator": indicator, "type": indicator_type,
                "malicious": stats.get("malicious", 0),
                "suspicious": stats.get("suspicious", 0),
                "reputation": data.get("reputation", 0),
            }
            if stats.get("malicious", 0) > 3:
                self.findings.append({"severity": "high", "type": "Malicious Infrastructure",
                                      "detail": f"{indicator} flagged by {stats['malicious']} engines"})
            return result
        return None

    def passive_dns_lookup(self, indicator):
        """Query passive DNS via SecurityTrails-style API."""
        resp = self._get(f"https://api.securitytrails.com/v1/domain/{indicator}/subdomains",
                         headers={"APIKEY": "demo"})
        if resp and resp.status_code == 200:
            return resp.json().get("subdomains", [])
        try:
            ips = socket.getaddrinfo(indicator, None)
            return list({addr[4][0] for addr in ips})
        except socket.gaierror:
            return []

    def get_ssl_certificate(self, host, port=443):
        """Retrieve SSL certificate details for fingerprinting."""
        try:
            ctx = ssl.create_default_context()
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
            with ctx.wrap_socket(socket.socket(), server_hostname=host) as s:
                s.settimeout(5)
                s.connect((host, port))
                cert = s.getpeercert(binary_form=True)
                cert_hash = hashlib.sha256(cert).hexdigest()
                der_info = s.getpeercert()
                return {
                    "host": host, "sha256": cert_hash,
                    "subject": dict(x[0] for x in der_info.get("subject", [])) if der_info else {},
                    "issuer": dict(x[0] for x in der_info.get("issuer", [])) if der_info else {},
                    "serial": der_info.get("serialNumber") if der_info else None,
                    "not_after": der_info.get("notAfter") if der_info else None,
                }
        except Exception:
            return None

    def check_whois(self, domain):
        """Retrieve WHOIS data via RDAP for pivoting."""
        resp = self._get(f"https://rdap.org/domain/{domain}")
        if resp and resp.status_code == 200:
            data = resp.json()
            registrar = None
            for entity in data.get("entities", []):
                if "registrar" in entity.get("roles", []):
                    registrar = entity.get("handle")
            return {
                "domain": domain, "status": data.get("status", []),
                "registrar": registrar,
                "nameservers": [ns.get("ldhName") for ns in data.get("nameservers", [])],
            }
        return None

    def fingerprint_http(self, ip, port=80):
        """Fingerprint HTTP server for infrastructure correlation."""
        resp = self._get(f"http://{ip}:{port}/", timeout=5)
        if not resp:
            return None
        headers = dict(resp.headers)
        body_hash = hashlib.sha256(resp.content).hexdigest()
        return {
            "ip": ip, "port": port, "status": resp.status_code,
            "server": headers.get("Server"), "content_type": headers.get("Content-Type"),
            "body_hash": body_hash, "body_length": len(resp.content),
            "headers_of_interest": {k: v for k, v in headers.items()
                                    if k.lower() not in ("date", "content-length", "connection")},
        }

    def pivot_from_ip(self, ip):
        """Perform infrastructure pivoting from a known IP."""
        result = {"ip": ip, "shodan": None, "vt": None, "ssl": None, "http": None}
        result["shodan"] = self.query_shodan(ip)
        result["vt"] = self.query_virustotal(ip, "ip")
        result["ssl"] = self.get_ssl_certificate(ip)
        result["http"] = self.fingerprint_http(ip)
        return result

    def generate_report(self, indicators=None):
        results = {}
        if indicators:
            for ind in indicators:
                results[ind] = self.pivot_from_ip(ind)

        report = {
            "report_date": datetime.utcnow().isoformat(),
            "indicators_analyzed": len(indicators or []),
            "pivot_results": results,
            "infrastructure_map": self.infrastructure,
            "findings": self.findings,
            "total_findings": len(self.findings),
        }
        out = self.output_dir / "threat_infra_report.json"
        with open(out, "w") as f:
            json.dump(report, f, indent=2)
        print(json.dumps(report, indent=2))
        return report


def main():
    if len(sys.argv) < 2:
        print("Usage: agent.py <ip_or_domain> [--shodan-key KEY] [--vt-key KEY]")
        sys.exit(1)
    indicators = [sys.argv[1]]
    shodan_key = vt_key = None
    if "--shodan-key" in sys.argv:
        shodan_key = sys.argv[sys.argv.index("--shodan-key") + 1]
    if "--vt-key" in sys.argv:
        vt_key = sys.argv[sys.argv.index("--vt-key") + 1]
    agent = ThreatInfraTracker(shodan_key, vt_key)
    agent.generate_report(indicators)


if __name__ == "__main__":
    main()
process.py10.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/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()

Assets 1

template.mdtext/markdown · 1.3 KB
Keep exploring