Linux.Detection.CVE202514847.MongoBleed

Hunts for evidence of CVE-2025-14847 (MongoBleed) exploitation on MongoDB servers.

MongoBleed is a memory disclosure vulnerability in MongoDB’s zlib decompression that allows attackers to extract sensitive data (credentials, session tokens, PII) from server memory by establishing many rapid connections without authentication.

Affected Versions

  • MongoDB 8.2.0 - 8.2.2 (fixed in 8.2.3)
  • MongoDB 8.0.0 - 8.0.16 (fixed in 8.0.17)
  • MongoDB 7.0.0 - 7.0.27 (fixed in 7.0.28)
  • MongoDB 6.0.0 - 6.0.26 (fixed in 6.0.27)
  • MongoDB 5.0.0 - 5.0.31 (fixed in 5.0.32)
  • MongoDB 4.4.0 - 4.4.29 (fixed in 4.4.30)
  • All MongoDB 4.2.x versions (EOL)
  • All MongoDB 4.0.x versions (EOL)
  • All MongoDB 3.6.x versions (EOL)

Detection Logic

The exploit creates thousands of connections to probe for memory leaks but never sends client metadata like legitimate MongoDB drivers do. This artifact detects the attack by analyzing connection patterns in MongoDB JSON logs:

  1. Parse MongoDB logs for connection events (id:22943) and metadata (id:51800)
  2. Aggregate connections per source IP within the time window
  3. Calculate metadata rate (legitimate drivers: ~90-100%, exploit: 0%)
  4. Calculate connection velocity (exploit: ~100,000+ connections/min)
  5. Flag IPs matching attack characteristics

Risk Levels

LevelCriteria
HIGH≥100 connections AND <10% metadata rate AND ≥500 conn/min
MEDIUM≥100 connections AND <10% metadata rate (low velocity)
LOW≥100 connections (normal metadata rate)
INFO<100 connections

Velocity-based detection reduces false positives from long-running low-activity scenarios while ensuring real attacks (100,000+ conn/min) are always flagged HIGH.

Sources

  • MongoDBLogAnalysis: Parse log files from disk
  • DockerMongoDBLogs: Parse logs from Docker containers (requires Docker daemon)
  • RawConnectionEvents: Detailed view of individual connection events

Sources are skipped when preconditions aren’t met (no Docker, empty container pattern). No Docker commands execute unless DockerContainerPattern is set and Docker is running.

Validation

Tested against real (POC) MongoBleed exploit traffic across MongoDB 6.0, 7.0, 8.0, and 8.2:

  • Attack traffic: 499 connections in 0.3s (111,716/min), 0% metadata → HIGH

Production traffic analysis (legitimate patterns observed over multiple days):

  • Normal velocity: 0.2–3.2 connections/min (vs exploit: 100,000+/min)
  • Normal metadata rate: 99–100% (vs exploit: 0%)
  • Daily connection volumes: 300–4,500 connections with consistent metadata

name: Linux.Detection.CVE202514847.MongoBleed
author: Eric Capuano (https://bsky.app/profile/eric.zip)
description: |
  Hunts for evidence of CVE-2025-14847 (MongoBleed) exploitation on MongoDB servers.

  MongoBleed is a memory disclosure vulnerability in MongoDB's zlib decompression
  that allows attackers to extract sensitive data (credentials, session tokens, PII)
  from server memory by establishing many rapid connections without authentication.

  ## Affected Versions

  - MongoDB 8.2.0 - 8.2.2 (fixed in 8.2.3)
  - MongoDB 8.0.0 - 8.0.16 (fixed in 8.0.17)
  - MongoDB 7.0.0 - 7.0.27 (fixed in 7.0.28)
  - MongoDB 6.0.0 - 6.0.26 (fixed in 6.0.27)
  - MongoDB 5.0.0 - 5.0.31 (fixed in 5.0.32)
  - MongoDB 4.4.0 - 4.4.29 (fixed in 4.4.30)
  - All MongoDB 4.2.x versions (EOL)
  - All MongoDB 4.0.x versions (EOL)
  - All MongoDB 3.6.x versions (EOL)

  ## Detection Logic

  The exploit creates thousands of connections to probe for memory leaks but never
  sends client metadata like legitimate MongoDB drivers do. This artifact detects
  the attack by analyzing connection patterns in MongoDB JSON logs:

  1. Parse MongoDB logs for connection events (id:22943) and metadata (id:51800)
  2. Aggregate connections per source IP within the time window
  3. Calculate metadata rate (legitimate drivers: ~90-100%, exploit: 0%)
  4. Calculate connection velocity (exploit: ~100,000+ connections/min)
  5. Flag IPs matching attack characteristics

  ## Risk Levels

  | Level  | Criteria |
  |--------|----------|
  | HIGH   | ≥100 connections AND <10% metadata rate AND ≥500 conn/min |
  | MEDIUM | ≥100 connections AND <10% metadata rate (low velocity) |
  | LOW    | ≥100 connections (normal metadata rate) |
  | INFO   | <100 connections |

  Velocity-based detection reduces false positives from long-running low-activity
  scenarios while ensuring real attacks (100,000+ conn/min) are always flagged HIGH.

  ## Sources

  - **MongoDBLogAnalysis**: Parse log files from disk
  - **DockerMongoDBLogs**: Parse logs from Docker containers (requires Docker daemon)
  - **RawConnectionEvents**: Detailed view of individual connection events

  Sources are skipped when preconditions aren't met (no Docker, empty container pattern).
  No Docker commands execute unless DockerContainerPattern is set and Docker is running.

  ## Validation

  Tested against real (POC) MongoBleed exploit traffic across MongoDB 6.0, 7.0, 8.0, and 8.2:
  - Attack traffic: 499 connections in 0.3s (111,716/min), 0% metadata → HIGH

  Production traffic analysis (legitimate patterns observed over multiple days):
  - Normal velocity: 0.2–3.2 connections/min (vs exploit: 100,000+/min)
  - Normal metadata rate: 99–100% (vs exploit: 0%)
  - Daily connection volumes: 300–4,500 connections with consistent metadata

reference:
  - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-14847
  - https://github.com/joe-desimone/mongobleed
  - https://www.ox.security/blog/attackers-could-exploit-zlib-to-exfiltrate-data-cve-2025-14847/

type: CLIENT

precondition: SELECT OS FROM info() WHERE OS = 'linux'

parameters:
  - name: MongoLogGlobs
    description: |
      JSON array of glob patterns to find MongoDB log files.
      Override for non-standard installations (e.g., custom paths, Atlas, containers).
      Default covers Ubuntu/Debian, RHEL/CentOS, and JSON log formats.
    type: string
    default: |
      ["/var/log/mongodb/*.log*", "/var/log/mongod.log*", "/var/log/mongo*.json*"]

  - name: DockerContainerPattern
    description: |
      Docker container name pattern to match (supports wildcards).
      Examples: "mongo*", "*mongodb*", "prod-mongo-*"
      Leave BLANK to skip Docker scanning entirely (no Docker commands will be executed).
    type: string
    default: ""

  - name: ConnectionThreshold
    description: Minimum connections from single IP to flag as suspicious
    type: int
    default: 100

  - name: MetadataRatioThreshold
    description: Maximum metadata/connection ratio to flag (0.1 = 10%)
    type: float
    default: 0.1

  - name: VelocityThreshold
    description: |
      Minimum connections per minute to consider as attack velocity.
      MongoBleed creates ~130,000 connections/minute. Default 500/min is conservative.
    type: int
    default: 500

  - name: TimeRangeMinutes
    description: Look back period in minutes
    type: int
    default: 60

sources:
  - name: MongoDBLogAnalysis
    description: Parse MongoDB logs and detect MongoBleed attack patterns
    query: |
      -- Calculate time range cutoff
      LET cutoff_time = timestamp(epoch=now() - TimeRangeMinutes * 60)

      -- Find MongoDB log files
      LET log_files = SELECT OSPath
        FROM glob(globs=parse_json_array(data=MongoLogGlobs))
        WHERE OSPath =~ "\\.(log|json)"

      -- Parse each log file line by line as JSON, filtered by time range
      LET parsed_logs = SELECT * FROM foreach(
        row=log_files,
        query={
          SELECT
            OSPath,
            parse_json(data=Line) AS LogEntry
          FROM parse_lines(filename=OSPath)
          WHERE Line =~ "^\\{"
            AND timestamp(string=parse_json(data=Line).t.`$date`) >= cutoff_time
        })

      -- Extract connection events
      LET connection_events = SELECT
        OSPath,
        LogEntry.t.`$date` AS Timestamp,
        LogEntry.id AS EventId,
        LogEntry.msg AS Message,
        LogEntry.attr.remote AS Remote,
        LogEntry.attr.connectionId AS ConnectionId,
        regex_replace(source=LogEntry.attr.remote, re=":\\d+$", replace="") AS SourceIP
      FROM parsed_logs
      WHERE LogEntry.id IN (22943, 51800, 22944)

      -- Aggregate by source IP
      LET aggregated = SELECT
        SourceIP,
        count() AS TotalEvents,
        sum(item=if(condition=(EventId = 22943), then=1, else=0)) AS Connections,
        sum(item=if(condition=(EventId = 51800), then=1, else=0)) AS MetadataEvents,
        sum(item=if(condition=(EventId = 22944), then=1, else=0)) AS Disconnections,
        min(item=Timestamp) AS FirstSeen,
        max(item=Timestamp) AS LastSeen
      FROM connection_events
      GROUP BY SourceIP

      -- Calculate velocity and detect attack pattern
      LET with_velocity = SELECT
        SourceIP,
        Connections,
        MetadataEvents,
        Disconnections,
        FirstSeen,
        LastSeen,
        (timestamp(string=LastSeen).UnixNano - timestamp(string=FirstSeen).UnixNano) / 1000000000.0 AS DurationSeconds,
        if(condition=((timestamp(string=LastSeen).UnixNano - timestamp(string=FirstSeen).UnixNano) > 0),
          then=Connections * 60.0 / ((timestamp(string=LastSeen).UnixNano - timestamp(string=FirstSeen).UnixNano) / 1000000000.0),
          else=Connections * 60.0) AS ConnectionsPerMinute
      FROM aggregated

      SELECT
        SourceIP,
        Connections,
        MetadataEvents,
        Disconnections,
        FirstSeen,
        LastSeen,
        format(format="%.1fs", args=[DurationSeconds]) AS Duration,
        format(format="%.0f/min", args=[ConnectionsPerMinute]) AS Velocity,
        if(condition=(Connections > 0),
          then=format(format="%.2f%%", args=[MetadataEvents * 100.0 / Connections]),
          else="N/A") AS MetadataRate,
        if(condition=(Connections >= ConnectionThreshold AND
                      (MetadataEvents * 1.0 / Connections) < MetadataRatioThreshold AND
                      ConnectionsPerMinute >= VelocityThreshold),
          then="HIGH - Likely MongoBleed Exploitation",
          else=if(condition=(Connections >= ConnectionThreshold AND
                            (MetadataEvents * 1.0 / Connections) < MetadataRatioThreshold),
            then="MEDIUM - Suspicious Pattern (low velocity)",
            else=if(condition=(Connections >= ConnectionThreshold),
              then="LOW - High Connection Rate",
              else="INFO"))) AS RiskLevel,
        "CVE-2025-14847" AS CVE,
        "MongoBleed MongoDB Memory Leak" AS ThreatName
      FROM with_velocity
      WHERE Connections > 10
      ORDER BY Connections DESC

  - name: DockerMongoDBLogs
    description: Parse MongoDB logs from Docker containers matching pattern
    query: |
      -- Check if Docker is available and pattern is configured
      LET docker_available = DockerContainerPattern != "" AND stat(filename="/var/run/docker.sock")

      -- Calculate time range
      LET since_duration = format(format="%dm", args=[TimeRangeMinutes])

      -- Find all containers matching the pattern (only if Docker available)
      LET matching_containers = SELECT * FROM if(condition=docker_available,
        then={
          SELECT
            parse_string_with_regex(
              string=Stdout,
              regex="^(?P<ContainerName>.+)$"
            ).ContainerName AS ContainerName
          FROM execve(
            argv=["docker", "ps", "--filter", "name=" + DockerContainerPattern, "--format", "{{.Names}}"],
            sep="\n"
          )
          WHERE Stdout != ""
        })

      -- Get docker logs from each matching container (use --since for efficiency)
      LET docker_logs = SELECT * FROM foreach(
        row=matching_containers,
        query={
          SELECT
            ContainerName,
            parse_json(data=Stdout) AS LogEntry,
            Stdout AS RawLine
          FROM execve(argv=["docker", "logs", ContainerName, "--since", since_duration, "--tail", "50000"], sep="\n")
          WHERE Stdout =~ "^\\{"
        })

      -- Extract connection events from docker logs
      LET docker_events = SELECT
        ContainerName,
        LogEntry.t.`$date` AS Timestamp,
        LogEntry.id AS EventId,
        LogEntry.msg AS Message,
        LogEntry.attr.remote AS Remote,
        LogEntry.attr.connectionId AS ConnectionId,
        regex_replace(source=LogEntry.attr.remote, re=":\\d+$", replace="") AS SourceIP
      FROM docker_logs
      WHERE LogEntry.id IN (22943, 51800, 22944)

      -- Aggregate by container and source IP
      LET docker_aggregated = SELECT
        ContainerName,
        SourceIP,
        count() AS TotalEvents,
        sum(item=if(condition=(EventId = 22943), then=1, else=0)) AS Connections,
        sum(item=if(condition=(EventId = 51800), then=1, else=0)) AS MetadataEvents,
        sum(item=if(condition=(EventId = 22944), then=1, else=0)) AS Disconnections,
        min(item=Timestamp) AS FirstSeen,
        max(item=Timestamp) AS LastSeen
      FROM docker_events
      GROUP BY ContainerName, SourceIP

      -- Calculate velocity and detect attack pattern
      LET docker_with_velocity = SELECT
        ContainerName,
        SourceIP,
        Connections,
        MetadataEvents,
        Disconnections,
        FirstSeen,
        LastSeen,
        (timestamp(string=LastSeen).UnixNano - timestamp(string=FirstSeen).UnixNano) / 1000000000.0 AS DurationSeconds,
        if(condition=((timestamp(string=LastSeen).UnixNano - timestamp(string=FirstSeen).UnixNano) > 0),
          then=Connections * 60.0 / ((timestamp(string=LastSeen).UnixNano - timestamp(string=FirstSeen).UnixNano) / 1000000000.0),
          else=Connections * 60.0) AS ConnectionsPerMinute
      FROM docker_aggregated

      SELECT
        ContainerName,
        SourceIP,
        Connections,
        MetadataEvents,
        Disconnections,
        FirstSeen,
        LastSeen,
        format(format="%.1fs", args=[DurationSeconds]) AS Duration,
        format(format="%.0f/min", args=[ConnectionsPerMinute]) AS Velocity,
        if(condition=(Connections > 0),
          then=format(format="%.2f%%", args=[MetadataEvents * 100.0 / Connections]),
          else="N/A") AS MetadataRate,
        if(condition=(Connections >= ConnectionThreshold AND
                      (MetadataEvents * 1.0 / Connections) < MetadataRatioThreshold AND
                      ConnectionsPerMinute >= VelocityThreshold),
          then="HIGH - Likely MongoBleed Exploitation",
          else=if(condition=(Connections >= ConnectionThreshold AND
                            (MetadataEvents * 1.0 / Connections) < MetadataRatioThreshold),
            then="MEDIUM - Suspicious Pattern (low velocity)",
            else=if(condition=(Connections >= ConnectionThreshold),
              then="LOW - High Connection Rate",
              else="INFO"))) AS RiskLevel,
        "CVE-2025-14847" AS CVE,
        "MongoBleed MongoDB Memory Leak" AS ThreatName
      FROM docker_with_velocity
      WHERE Connections > 10
      ORDER BY Connections DESC

  - name: RawConnectionEvents
    description: Raw connection events for detailed analysis
    query: |
      -- Calculate time range cutoff
      LET cutoff_time = timestamp(epoch=now() - TimeRangeMinutes * 60)

      -- Find MongoDB log files
      LET log_files = SELECT OSPath
        FROM glob(globs=parse_json_array(data=MongoLogGlobs))
        WHERE OSPath =~ "\\.(log|json)"

      -- Parse and return raw connection events, filtered by time range
      SELECT
        OSPath AS LogFile,
        parse_json(data=Line).t.`$date` AS Timestamp,
        parse_json(data=Line).id AS EventId,
        parse_json(data=Line).msg AS Message,
        parse_json(data=Line).attr.remote AS Remote,
        parse_json(data=Line).attr.connectionId AS ConnectionId,
        parse_json(data=Line).attr.connectionCount AS ConnectionCount
      FROM foreach(
        row=log_files,
        query={
          SELECT OSPath, Line
          FROM parse_lines(filename=OSPath)
          WHERE Line =~ "^\\{" AND Line =~ '"id":(22943|51800|22944)'
            AND timestamp(string=parse_json(data=Line).t.`$date`) >= cutoff_time
        })
      ORDER BY Timestamp DESC
      LIMIT 1000