CVE-2024-21509
Description
mysql2 before 3.9.4 is vulnerable to prototype pollution via insecure result object creation, allowing remote code execution when processing attacker-controlled query configuration.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
mysql2 before 3.9.4 is vulnerable to prototype pollution via insecure result object creation, allowing remote code execution when processing attacker-controlled query configuration.
Vulnerability
Description
Versions of the mysql2 package before 3.9.4 are vulnerable to prototype pollution, also known as prototype poisoning, due to insecure creation of result objects and improper sanitization of user input passed through the parserFn in text_parser.js and binary_parser.js [1]. The library exposes the ability for an attacker to inject arbitrary properties into the global Object.prototype when parsing query results, which can lead to severe security implications.
Exploitation
An attacker can exploit this vulnerability by providing a malicious database connection with specially crafted query configuration parameters [1]. When the application processes queries from such a connection, the unsafe result object construction allows the attacker to pollute the prototype chain. This can be achieved by setting properties like __proto__ or constructor on the result objects, which then propagate to all JavaScript objects, as detailed in the Snyk advisory [4]. The vulnerability is triggered without authentication if the attacker can control the database connection settings.
Impact
Successful exploitation can lead to denial of service by causing JavaScript exceptions, or, more critically, to remote code execution by manipulating the application's code path [1]. The attacker may gain the ability to execute arbitrary commands on the server, compromising the entire application and its data.
Mitigation
The issue has been fixed in mysql2 version 3.9.4. The fix ensures that result objects are created with Object.create(null) and that the constructor property is properly protected against pollution [2]. Users are strongly advised to upgrade to the latest version. No workarounds are available for older versions.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
mysql2npm | < 3.9.4 | 3.9.4 |
Affected products
6- mysql2/mysql2description
- osv-coords5 versionspkg:apk/chainguard/sqlpadpkg:apk/chainguard/sqlpad-compatpkg:apk/wolfi/sqlpadpkg:apk/wolfi/sqlpad-compatpkg:npm/mysql2
< 7.4.1-r4+ 4 more
- (no CPE)range: < 7.4.1-r4
- (no CPE)range: < 7.4.1-r4
- (no CPE)range: < 7.4.1-r4
- (no CPE)range: < 7.4.1-r4
- (no CPE)range: < 3.9.4
Patches
14a964a3910a4fix(security): improve results object creation (#2574)
6 files changed · +166 −12
lib/parsers/binary_parser.js+10 −2 modified@@ -122,7 +122,13 @@ function compile(fields, options, config) { if (options.rowsAsArray) { parserFn(`const result = new Array(${fields.length});`); } else { - parserFn('const result = {};'); + parserFn('const result = Object.create(null);'); + parserFn(`Object.defineProperty(result, "constructor", { + value: Object.create(null), + writable: false, + configurable: false, + enumerable: false + });`); } // Global typeCast @@ -154,7 +160,9 @@ function compile(fields, options, config) { )}]`; } else if (options.nestTables === true) { tableName = helpers.srcEscape(fields[i].table); - parserFn(`if (!result[${tableName}]) result[${tableName}] = {};`); + parserFn( + `if (!result[${tableName}]) result[${tableName}] = Object.create(null);`, + ); lvalue = `result[${tableName}][${fieldName}]`; } else if (options.rowsAsArray) { lvalue = `result[${i.toString(10)}]`;
lib/parsers/text_parser.js+10 −9 modified@@ -111,9 +111,6 @@ function compile(fields, options, config) { const parserFn = genFunc(); - /* eslint-disable no-trailing-spaces */ - /* eslint-disable no-spaced-func */ - /* eslint-disable no-unexpected-multiline */ parserFn('(function () {')('return class TextRow {'); // constructor method @@ -134,7 +131,13 @@ function compile(fields, options, config) { if (options.rowsAsArray) { parserFn(`const result = new Array(${fields.length});`); } else { - parserFn('const result = {};'); + parserFn('const result = Object.create(null);'); + parserFn(`Object.defineProperty(result, "constructor", { + value: Object.create(null), + writable: false, + configurable: false, + enumerable: false + });`); } const resultTables = {}; @@ -146,7 +149,9 @@ function compile(fields, options, config) { } resultTablesArray = Object.keys(resultTables); for (let i = 0; i < resultTablesArray.length; i++) { - parserFn(`result[${helpers.srcEscape(resultTablesArray[i])}] = {};`); + parserFn( + `result[${helpers.srcEscape(resultTablesArray[i])}] = Object.create(null);`, + ); } } @@ -191,10 +196,6 @@ function compile(fields, options, config) { parserFn('}'); parserFn('};')('})()'); - /* eslint-enable no-trailing-spaces */ - /* eslint-enable no-spaced-func */ - /* eslint-enable no-unexpected-multiline */ - if (config.debug) { helpers.printDebugWithCode( 'Compiled text protocol row parser',
.nycrc+1 −1 modified@@ -5,7 +5,7 @@ "reporter": ["text", "lcov", "cobertura"], "statements": 88, "branches": 84, - "functions": 78, + "functions": 77, "lines": 88, "checkCoverage": true, "clean": true
test/common.test.cjs+1 −0 modified@@ -105,6 +105,7 @@ exports.createConnection = function (args) { typeCast: args && args.typeCast, namedPlaceholders: args && args.namedPlaceholders, connectTimeout: args && args.connectTimeout, + nestTables: args && args.nestTables, ssl: (args && args.ssl) ?? config.ssl, };
test/esm/unit/parsers/prototype-binary-results.test.mjs+72 −0 added@@ -0,0 +1,72 @@ +import { test, describe, assert } from 'poku'; +import { createConnection, describeOptions } from '../../../common.test.cjs'; + +const connection = createConnection().promise(); + +describe('Binary Parser: Prototype Sanitization', describeOptions); + +Promise.all([ + test(async () => { + const expected = [{}]; + expected[0].test = 2; + + const [results] = await connection.query('SELECT 1+1 AS `test`'); + + assert.notDeepStrictEqual( + results, + expected, + `Ensure "results" doesn't contain a standard object ({})`, + ); + }), + test(async () => { + const expected = [Object.create(null)]; + expected[0].test = 2; + + const [results] = await connection.execute('SELECT 1+1 AS `test`'); + + assert.deepStrictEqual(results, expected, 'Ensure clean object "results"'); + assert.strictEqual( + Object.getPrototypeOf(results[0]), + null, + 'Ensure clean properties in results items', + ); + assert.strictEqual( + typeof results[0].toString, + 'undefined', + 'Re-check prototypes (manually) in results columns', + ); + assert.strictEqual( + typeof results[0].test.toString, + 'function', + 'Ensure that the end-user is able to use prototypes', + ); + assert.strictEqual( + results[0].test.toString(), + '2', + 'Ensure that the end-user is able to use prototypes (manually): toString', + ); + assert.strictEqual( + results[0].test.toFixed(2), + '2.00', + 'Ensure that the end-user is able to use prototypes (manually): toFixed', + ); + + results[0].customProp = true; + assert.strictEqual( + results[0].customProp, + true, + 'Ensure that the end-user is able to use custom props', + ); + }), + test(async () => { + const [result] = await connection.execute('SET @1 = 1;'); + + assert.strictEqual( + result.constructor.name, + 'ResultSetHeader', + 'Ensure constructor name in result object', + ); + }), +]).then(async () => { + await connection.end(); +});
test/esm/unit/parsers/prototype-text-results.test.mjs+72 −0 added@@ -0,0 +1,72 @@ +import { test, describe, assert } from 'poku'; +import { createConnection, describeOptions } from '../../../common.test.cjs'; + +const connection = createConnection().promise(); + +describe('Text Parser: Prototype Sanitization', describeOptions); + +Promise.all([ + test(async () => { + const expected = [{}]; + expected[0].test = 2; + + const [results] = await connection.query('SELECT 1+1 AS `test`'); + + assert.notDeepStrictEqual( + results, + expected, + `Ensure "results" doesn't contain a standard object ({})`, + ); + }), + test(async () => { + const expected = [Object.create(null)]; + expected[0].test = 2; + + const [results] = await connection.query('SELECT 1+1 AS `test`'); + + assert.deepStrictEqual(results, expected, 'Ensure clean object "results"'); + assert.strictEqual( + Object.getPrototypeOf(results[0]), + null, + 'Ensure clean properties in results items', + ); + assert.strictEqual( + typeof results[0].toString, + 'undefined', + 'Re-check prototypes (manually) in results columns', + ); + assert.strictEqual( + typeof results[0].test.toString, + 'function', + 'Ensure that the end-user is able to use prototypes', + ); + assert.strictEqual( + results[0].test.toString(), + '2', + 'Ensure that the end-user is able to use prototypes (manually): toString', + ); + assert.strictEqual( + results[0].test.toFixed(2), + '2.00', + 'Ensure that the end-user is able to use prototypes (manually): toFixed', + ); + + results[0].customProp = true; + assert.strictEqual( + results[0].customProp, + true, + 'Ensure that the end-user is able to use custom props', + ); + }), + test(async () => { + const [result] = await connection.query('SET @1 = 1;'); + + assert.strictEqual( + result.constructor.name, + 'ResultSetHeader', + 'Ensure constructor name in result object', + ); + }), +]).then(async () => { + await connection.end(); +});
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-49j4-86m8-q2jwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-21509ghsaADVISORY
- blog.slonser.info/posts/mysql2-attacker-configurationghsaWEB
- github.com/sidorares/node-mysql2/blob/fd3d117da82cc5c5fa5a3701d7b33ca77691bc61/lib/parsers/text_parser.js%23L134ghsaWEB
- github.com/sidorares/node-mysql2/commit/4a964a3910a4b8de008696c554ab1b492e9b4691ghsaWEB
- github.com/sidorares/node-mysql2/pull/2574ghsaWEB
- github.com/sidorares/node-mysql2/releases/tag/v3.9.4ghsaWEB
- security.snyk.io/vuln/SNYK-JS-MYSQL2-6591084ghsaWEB
- blog.slonser.info/posts/mysql2-attacker-configuration/mitre
News mentions
0No linked articles in our index yet.