CVE-2024-21509 (JavaScript, published 2024-04-10T05:15Z)
Description: value: Versions of the package mysql2 before 3.9.4 are vulnerable to Prototype Poisoning due to insecure results object creation and improper user input sanitization passed through parserFn in text_parser.js and binary_parser.js.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/sidorares/node-mysql2
Files changed:
.nycrc
lib/parsers/binary_parser.js
lib/parsers/text_parser.js
test/common.test.cjs
test/esm/unit/parsers/prototype-binary-results.test.mjs
test/esm/unit/parsers/prototype-text-results.test.mjs
Commit message: fix(security): improve results object creation (#2574)

Fixes a potential Prototype Pollution attack vulnerability reported by Vsevolod Kokorin (Slonser) of Solidlab

Vulnerable code:
  "functions": 78,
    parserFn('const result = {};');
      parserFn(`if (!result[${tableName}]) result[${tableName}] = {};`);
  /* eslint-disable no-trailing-spaces */
  /* eslint-disable no-spaced-func */
  /* eslint-disable no-unexpected-multiline */
    parserFn('const result = {};');
      parserFn(`result[${helpers.srcEscape(resultTablesArray[i])}] = {};`);
  /* eslint-enable no-trailing-spaces */
  /* eslint-enable no-spaced-func */
  /* eslint-enable no-unexpected-multiline */


Fixed code:
  "functions": 77,
    parserFn('const result = Object.create(null);');
    parserFn(`Object.defineProperty(result, "constructor", {
      value: Object.create(null),
      writable: false,
      configurable: false,
      enumerable: false
    });`);
      parserFn(
        `if (!result[${tableName}]) result[${tableName}] = Object.create(null);`,
      );
    parserFn('const result = Object.create(null);');
    parserFn(`Object.defineProperty(result, "constructor", {
      value: Object.create(null),
      writable: false,
      configurable: false,
      enumerable: false
    });`);
      parserFn(
        `result[${helpers.srcEscape(resultTablesArray[i])}] = Object.create(null);`,
      );
    nestTables: args && args.nestTables,
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();
});
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();
});