VYPR
Moderate severityNVD Advisory· Published Apr 10, 2024· Updated Aug 22, 2024

CVE-2024-21509

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.

PackageAffected versionsPatched versions
mysql2npm
< 3.9.43.9.4

Affected products

6

Patches

1
4a964a3910a4

fix(security): improve results object creation (#2574)

https://github.com/sidorares/node-mysql2Weslley AraújoApr 9, 2024via ghsa
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

News mentions

0

No linked articles in our index yet.