threat intelligence

Analyzing Ransomware Leak Site Intelligence

Monitor and analyze ransomware group data leak sites (DLS) to track victim postings, extract threat intelligence on group tactics, and assess sector-specific ransomware risk for proactive defense.

data-leakdlsextortionleak-siteleak-site-monitoringransomwarethreat-intelligencevictim-tracking
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Ransomware groups operating under double-extortion models maintain data leak sites (DLS) on Tor hidden services where they post victim names, stolen data samples, and countdown timers to pressure payment. In H1 2025, 96 unique ransomware groups were active, listing approximately 535 victims per month. Monitoring these sites provides intelligence on active threat groups, targeted sectors, geographic patterns, and emerging ransomware families. This skill covers safely collecting DLS intelligence, extracting structured data, tracking group activity trends, and producing sector-specific risk assessments.

When to Use

  • When investigating security incidents that require analyzing ransomware leak site intelligence
  • When building detection rules or threat hunting queries for this domain
  • When SOC analysts need structured procedures for this analysis type
  • When validating security monitoring coverage for related attack techniques

Prerequisites

  • Python 3.9+ with requests, beautifulsoup4, pandas, matplotlib libraries
  • Tor proxy (SOCKS5) for accessing .onion sites or commercial DLS monitoring feeds
  • Understanding of ransomware double-extortion business model
  • Familiarity with major ransomware families (Qilin, Akira, LockBit, BlackCat, Clop)
  • Access to ransomware tracking feeds (Ransomwatch, RansomLook, DarkFeed)

Key Concepts

Double Extortion Model

Modern ransomware groups encrypt victim data AND exfiltrate it before encryption. Leak sites serve as public pressure: victims are listed with a countdown timer, partial data samples, and file trees. If ransom is not paid, full data is published. Some groups have moved to triple extortion, adding DDoS threats or contacting victims' customers directly.

DLS Intelligence Value

Leak sites provide: victim identification (company name, sector, country), attack timeline (when listed, deadline, data published), data volume estimates, group capability assessment (sectors targeted, attack frequency, operational tempo), and trend analysis (new groups emerging, groups rebranding, law enforcement takedowns).

Safe Collection Practices

Never directly access DLS sites in a production environment. Use purpose-built monitoring services (Ransomwatch, DarkFeed, KELA, Flashpoint), Tor-isolated research VMs, commercial threat intelligence platforms, or community-maintained datasets. All analysis should be conducted in isolated environments with proper authorization.

Workflow

Step 1: Ingest Ransomware Leak Site Data from Public Feeds

import requests
import json
import pandas as pd
from datetime import datetime, timedelta
from collections import Counter
 
class RansomwareIntelCollector:
    """Collect ransomware DLS intelligence from public tracking sources."""
 
    RANSOMWATCH_API = "https://raw.githubusercontent.com/joshhighet/ransomwatch/main/posts.json"
    RANSOMWATCH_GROUPS = "https://raw.githubusercontent.com/joshhighet/ransomwatch/main/groups.json"
 
    def __init__(self):
        self.posts = []
        self.groups = []
 
    def fetch_ransomwatch_data(self):
        """Fetch ransomware victim posts from ransomwatch."""
        resp = requests.get(self.RANSOMWATCH_API, timeout=30)
        if resp.status_code == 200:
            self.posts = resp.json()
            print(f"[+] Loaded {len(self.posts)} victim posts from ransomwatch")
        else:
            print(f"[-] Failed to fetch posts: {resp.status_code}")
 
        resp = requests.get(self.RANSOMWATCH_GROUPS, timeout=30)
        if resp.status_code == 200:
            self.groups = resp.json()
            print(f"[+] Loaded {len(self.groups)} ransomware group profiles")
 
        return self.posts
 
    def get_recent_victims(self, days=30):
        """Get victims posted in the last N days."""
        cutoff = datetime.now() - timedelta(days=days)
        recent = []
        for post in self.posts:
            try:
                discovered = datetime.fromisoformat(
                    post.get("discovered", "").replace("Z", "+00:00")
                )
                if discovered.replace(tzinfo=None) >= cutoff:
                    recent.append(post)
            except (ValueError, TypeError):
                continue
        print(f"[+] {len(recent)} victims in last {days} days")
        return recent
 
    def get_group_activity(self, group_name):
        """Get all posts by a specific ransomware group."""
        group_posts = [
            p for p in self.posts
            if p.get("group_name", "").lower() == group_name.lower()
        ]
        print(f"[+] {group_name}: {len(group_posts)} total victims")
        return group_posts
 
collector = RansomwareIntelCollector()
collector.fetch_ransomwatch_data()
recent = collector.get_recent_victims(days=30)

Step 2: Analyze Group Activity and Trends

def analyze_group_trends(posts, top_n=15):
    """Analyze ransomware group activity trends."""
    group_counts = Counter(p.get("group_name", "unknown") for p in posts)
    monthly_activity = {}
 
    for post in posts:
        try:
            date = datetime.fromisoformat(
                post.get("discovered", "").replace("Z", "+00:00")
            )
            month_key = date.strftime("%Y-%m")
            group = post.get("group_name", "unknown")
            if month_key not in monthly_activity:
                monthly_activity[month_key] = Counter()
            monthly_activity[month_key][group] += 1
        except (ValueError, TypeError):
            continue
 
    analysis = {
        "total_posts": len(posts),
        "unique_groups": len(group_counts),
        "top_groups": group_counts.most_common(top_n),
        "monthly_totals": {
            month: sum(counts.values())
            for month, counts in sorted(monthly_activity.items())
        },
        "monthly_top_groups": {
            month: counts.most_common(5)
            for month, counts in sorted(monthly_activity.items())
        },
    }
 
    print(f"\n=== Ransomware Group Activity ===")
    print(f"Total victims tracked: {analysis['total_posts']}")
    print(f"Active groups: {analysis['unique_groups']}")
    print(f"\nTop {top_n} Groups:")
    for group, count in analysis["top_groups"]:
        print(f"  {group}: {count} victims")
 
    return analysis
 
trends = analyze_group_trends(collector.posts)

Step 3: Sector and Geographic Risk Assessment

def assess_sector_risk(posts, target_sector=None, target_country=None):
    """Assess ransomware risk for specific sector or geography."""
    sector_data = {}
    country_data = {}
 
    for post in posts:
        # Extract sector if available (not all feeds include this)
        sector = post.get("sector", post.get("industry", "unknown"))
        country = post.get("country", "unknown")
 
        if sector not in sector_data:
            sector_data[sector] = {"count": 0, "groups": Counter(), "recent": []}
        sector_data[sector]["count"] += 1
        sector_data[sector]["groups"][post.get("group_name", "")] += 1
 
        if country not in country_data:
            country_data[country] = {"count": 0, "groups": Counter()}
        country_data[country]["count"] += 1
        country_data[country]["groups"][post.get("group_name", "")] += 1
 
    # Sector risk scoring
    total = len(posts)
    risk_assessment = {
        "total_victims": total,
        "sectors": {},
        "countries": {},
    }
 
    for sector, data in sorted(sector_data.items(), key=lambda x: -x[1]["count"]):
        pct = (data["count"] / total * 100) if total > 0 else 0
        risk_assessment["sectors"][sector] = {
            "victim_count": data["count"],
            "percentage": round(pct, 1),
            "top_groups": data["groups"].most_common(5),
            "risk_level": (
                "critical" if pct > 15
                else "high" if pct > 8
                else "medium" if pct > 3
                else "low"
            ),
        }
 
    for country, data in sorted(country_data.items(), key=lambda x: -x[1]["count"]):
        pct = (data["count"] / total * 100) if total > 0 else 0
        risk_assessment["countries"][country] = {
            "victim_count": data["count"],
            "percentage": round(pct, 1),
            "top_groups": data["groups"].most_common(5),
        }
 
    return risk_assessment
 
risk = assess_sector_risk(collector.posts)

Step 4: Track Emerging and Rebranding Groups

def track_new_groups(posts, lookback_days=90):
    """Identify newly emerged ransomware groups."""
    group_first_seen = {}
    for post in posts:
        group = post.get("group_name", "")
        try:
            date = datetime.fromisoformat(
                post.get("discovered", "").replace("Z", "+00:00")
            )
            if group not in group_first_seen or date < group_first_seen[group]["first_seen"]:
                group_first_seen[group] = {
                    "first_seen": date,
                    "first_victim": post.get("post_title", ""),
                }
        except (ValueError, TypeError):
            continue
 
    cutoff = datetime.now() - timedelta(days=lookback_days)
    new_groups = {
        group: info for group, info in group_first_seen.items()
        if info["first_seen"].replace(tzinfo=None) >= cutoff
    }
 
    # Count total victims per new group
    for group in new_groups:
        victims = [p for p in posts if p.get("group_name") == group]
        new_groups[group]["total_victims"] = len(victims)
        new_groups[group]["avg_per_month"] = round(
            len(victims) / max(1, lookback_days / 30), 1
        )
 
    print(f"\n=== New Groups (last {lookback_days} days) ===")
    for group, info in sorted(new_groups.items(), key=lambda x: -x[1]["total_victims"]):
        print(f"  {group}: {info['total_victims']} victims, "
              f"first seen {info['first_seen'].strftime('%Y-%m-%d')}")
 
    return new_groups
 
new_groups = track_new_groups(collector.posts, lookback_days=90)

Step 5: Generate Intelligence Report

def generate_ransomware_intel_report(trends, risk, new_groups):
    """Generate ransomware threat intelligence report."""
    report = f"""# Ransomware Threat Intelligence Report
Generated: {datetime.now().isoformat()}
 
## Executive Summary
- **Total victims tracked**: {trends['total_posts']}
- **Active ransomware groups**: {trends['unique_groups']}
- **New groups (last 90 days)**: {len(new_groups)}
 
## Top Active Groups
| Rank | Group | Victims |
|------|-------|---------|
"""
    for i, (group, count) in enumerate(trends["top_groups"][:10], 1):
        report += f"| {i} | {group} | {count} |\n"
 
    report += "\n## New Emerging Groups\n"
    for group, info in sorted(new_groups.items(), key=lambda x: -x[1]["total_victims"])[:10]:
        report += f"- **{group}**: {info['total_victims']} victims since {info['first_seen'].strftime('%Y-%m-%d')}\n"
 
    report += "\n## Sector Risk Assessment\n"
    report += "| Sector | Victims | % | Risk Level |\n|--------|---------|---|------------|\n"
    for sector, data in list(risk["sectors"].items())[:10]:
        report += f"| {sector} | {data['victim_count']} | {data['percentage']}% | {data['risk_level'].upper()} |\n"
 
    report += """
## Recommendations
1. Monitor DLS feeds daily for your organization and supply chain partners
2. Prioritize patching vulnerabilities exploited by top active groups
3. Implement offline backup strategy to reduce extortion leverage
4. Conduct tabletop exercises for ransomware scenario response
5. Share indicators with sector ISACs and threat sharing communities
"""
    with open("ransomware_intel_report.md", "w") as f:
        f.write(report)
    print("[+] Report saved: ransomware_intel_report.md")
    return report
 
generate_ransomware_intel_report(trends, risk, new_groups)

Validation Criteria

  • Ransomware victim data ingested from public tracking feeds
  • Group activity trends analyzed with monthly breakdowns
  • Sector and geographic risk assessment produced
  • New and emerging groups identified with activity metrics
  • Intelligence report generated with actionable recommendations
  • All collection conducted through authorized public sources

References

Source materials

References and resources

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

References 1

api-reference.md2.3 KB

API Reference: Ransomware Leak Site Intelligence

ransomware.live API

Recent Victims

curl https://api.ransomware.live/recentvictims

Group Information

curl https://api.ransomware.live/groups
curl https://api.ransomware.live/group/lockbit3

Response Format

{
  "group_name": "lockbit3",
  "victim": "company-name",
  "website": "company.com",
  "discovered": "2024-03-15T00:00:00Z",
  "country": "US",
  "activity": "Manufacturing"
}

ransomlook.io API

Endpoints

curl https://www.ransomlook.io/api/groups       # List all groups
curl https://www.ransomlook.io/api/group/lockbit # Group details
curl https://www.ransomlook.io/api/recent        # Recent posts

Ransomwatch (GitHub)

Data Repository

git clone https://github.com/joshhighet/ransomwatch
# Data in JSON format: posts.json, groups.json

JSON Schema

{
  "group_name": "string",
  "post_title": "string",
  "discovered": "ISO-8601",
  "post_url": "onion URL",
  "country": "2-letter code",
  "activity": "sector"
}

ID Ransomware

Identification

Upload: encrypted file + ransom note
URL: https://id-ransomware.malwarehunterteam.com/
Returns: ransomware family, decryptor availability

Active Ransomware Groups (2025)

Group Status Primary Target
LockBit 3.0 Active Cross-sector
Cl0p Active MOVEit/file transfer exploitation
Play Active Manufacturing, IT
8Base Active SMBs
Akira Active Healthcare, Education
Black Basta Active Enterprise
Medusa Active Education, Healthcare
RansomHub Active Cross-sector
Rhysida Active Government, Healthcare
BianLian Active Healthcare, Manufacturing

Intelligence Collection Framework

Source Type Update Frequency
ransomware.live Victim listings Real-time
ransomlook.io Group monitoring Daily
ransomwatch Onion site scraping Hourly
NoMoreRansom.org Decryptor availability As released
CISA alerts Government advisories As published

STIX Representation

{
  "type": "threat-actor",
  "name": "LockBit",
  "threat_actor_types": ["crime-syndicate"],
  "roles": ["agent"],
  "goals": ["financial-gain"]
}

Scripts 1

agent.py6.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Ransomware leak site intelligence analysis agent.

Monitors and analyzes ransomware group leak site data for threat intelligence,
victim tracking, and TTI (time-to-intelligence) reporting.
"""

import sys
import json
from datetime import datetime, timedelta
from collections import defaultdict, Counter

try:
    import requests
    HAS_REQUESTS = True
except ImportError:
    HAS_REQUESTS = False

RANSOMWARE_GROUPS = {
    "lockbit": {"aliases": ["LockBit 3.0", "LockBit Black"], "status": "active"},
    "alphv": {"aliases": ["BlackCat", "ALPHV"], "status": "disrupted"},
    "cl0p": {"aliases": ["Clop", "TA505"], "status": "active"},
    "play": {"aliases": ["PlayCrypt"], "status": "active"},
    "8base": {"aliases": ["8Base"], "status": "active"},
    "akira": {"aliases": ["Akira"], "status": "active"},
    "bianlian": {"aliases": ["BianLian"], "status": "active"},
    "blackbasta": {"aliases": ["Black Basta"], "status": "active"},
    "medusa": {"aliases": ["MedusaLocker", "Medusa Blog"], "status": "active"},
    "rhysida": {"aliases": ["Rhysida"], "status": "active"},
    "royal": {"aliases": ["Royal", "BlackSuit"], "status": "rebranded"},
    "ransomhub": {"aliases": ["RansomHub"], "status": "active"},
}


def query_ransomwatch_api():
    """Query ransomwatch or ransomware.live API for leak site data."""
    if not HAS_REQUESTS:
        return []
    try:
        resp = requests.get("https://api.ransomware.live/recentvictims",
                           timeout=30)
        resp.raise_for_status()
        return resp.json()
    except requests.RequestException as e:
        return [{"error": str(e)}]


def query_ransomlook_group(group_name):
    """Query ransomlook.io API for group information."""
    if not HAS_REQUESTS:
        return {}
    try:
        resp = requests.get(f"https://www.ransomlook.io/api/group/{group_name}",
                           timeout=30)
        resp.raise_for_status()
        return resp.json()
    except requests.RequestException:
        return {}


def analyze_victim_data(victims):
    """Analyze victim listing data for intelligence."""
    sector_counts = Counter()
    country_counts = Counter()
    group_counts = Counter()
    timeline = defaultdict(int)

    for v in victims:
        group = v.get("group_name", v.get("group", "unknown")).lower()
        group_counts[group] += 1
        sector = v.get("activity", v.get("sector", "unknown"))
        if sector:
            sector_counts[sector] += 1
        country = v.get("country", "unknown")
        if country:
            country_counts[country] += 1
        date_str = v.get("discovered", v.get("published", ""))
        if date_str:
            try:
                dt = datetime.fromisoformat(date_str.replace("Z", "+00:00"))
                timeline[dt.strftime("%Y-%m")] += 1
            except (ValueError, TypeError):
                pass

    return {
        "total_victims": len(victims),
        "top_groups": dict(group_counts.most_common(10)),
        "top_sectors": dict(sector_counts.most_common(10)),
        "top_countries": dict(country_counts.most_common(10)),
        "monthly_trend": dict(sorted(timeline.items())),
    }


def search_victims(victims, query):
    """Search victims by name, domain, or sector."""
    results = []
    query_lower = query.lower()
    for v in victims:
        name = (v.get("victim", v.get("post_title", "")) or "").lower()
        website = (v.get("website", "") or "").lower()
        sector = (v.get("activity", v.get("sector", "")) or "").lower()
        if query_lower in name or query_lower in website or query_lower in sector:
            results.append(v)
    return results


def assess_group_activity(victims, group_name, days=90):
    """Assess activity level of a specific ransomware group."""
    cutoff = datetime.now() - timedelta(days=days)
    group_victims = []
    for v in victims:
        g = (v.get("group_name", v.get("group", "")) or "").lower()
        if group_name.lower() in g:
            group_victims.append(v)

    recent = []
    for v in group_victims:
        date_str = v.get("discovered", v.get("published", ""))
        if date_str:
            try:
                dt = datetime.fromisoformat(date_str.replace("Z", "+00:00"))
                if dt.replace(tzinfo=None) > cutoff:
                    recent.append(v)
            except (ValueError, TypeError):
                pass

    info = RANSOMWARE_GROUPS.get(group_name.lower(), {})
    return {
        "group": group_name,
        "aliases": info.get("aliases", []),
        "status": info.get("status", "unknown"),
        "total_victims": len(group_victims),
        "recent_victims": len(recent),
        "period_days": days,
        "activity_level": "HIGH" if len(recent) > 20 else "MEDIUM" if len(recent) > 5 else "LOW",
    }


def generate_intelligence_report(victims, target_org=None):
    """Generate ransomware threat intelligence report."""
    analysis = analyze_victim_data(victims)
    report = {
        "report_date": datetime.now().isoformat(),
        "data_source": "ransomware.live API",
        "analysis": analysis,
    }
    if target_org:
        matches = search_victims(victims, target_org)
        report["org_search"] = {
            "query": target_org,
            "matches": len(matches),
            "results": matches[:10],
        }
    return report


if __name__ == "__main__":
    print("=" * 60)
    print("Ransomware Leak Site Intelligence Agent")
    print("Victim tracking, group analysis, sector trends")
    print("=" * 60)

    query = sys.argv[1] if len(sys.argv) > 1 else None

    if not HAS_REQUESTS:
        print("[!] Install requests: pip install requests")
        sys.exit(1)

    print("\n[*] Fetching recent ransomware victims...")
    victims = query_ransomwatch_api()
    if not victims or (len(victims) == 1 and "error" in victims[0]):
        print(f"[!] API error: {victims}")
        sys.exit(1)

    print(f"[*] Retrieved {len(victims)} victim entries")
    report = generate_intelligence_report(victims, target_org=query)
    analysis = report["analysis"]

    print(f"\n--- Top Groups ---")
    for g, c in list(analysis["top_groups"].items())[:5]:
        print(f"  {g:20s} {c} victims")

    print(f"\n--- Top Sectors ---")
    for s, c in list(analysis["top_sectors"].items())[:5]:
        print(f"  {s:30s} {c}")

    print(f"\n--- Top Countries ---")
    for co, c in list(analysis["top_countries"].items())[:5]:
        print(f"  {co:20s} {c}")

    if query:
        matches = report.get("org_search", {})
        print(f"\n--- Search: '{query}' ({matches.get('matches', 0)} results) ---")
        for m in matches.get("results", [])[:5]:
            print(f"  {m.get('group_name', '?'):15s} | {m.get('victim', m.get('post_title', '?'))}")

    print(f"\n{json.dumps(report, indent=2, default=str)}")
Keep exploring