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.
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:
| 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 are skipped when preconditions aren’t met (no Docker, empty container pattern). No Docker commands execute unless DockerContainerPattern is set and Docker is running.
Tested against real (POC) MongoBleed exploit traffic across MongoDB 6.0, 7.0, 8.0, and 8.2:
Production traffic analysis (legitimate patterns observed over multiple days):
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