CVE-2024-21512 (JavaScript, published 2024-05-29T05:16Z)
Description: value: Versions of the package mysql2 before 3.9.8 are vulnerable to Prototype Pollution due to improper user input sanitization passed to fields and tables when using nestTables.
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:
lib/helpers.js
lib/parsers/binary_parser.js
lib/parsers/text_parser.js
test/esm/unit/parsers/ensure-safe-binary-fields.test.mjs
test/esm/unit/parsers/ensure-safe-text-fields.test.mjs
Commit message: fix(security): sanitize fields and tables when using nestTables (#2702)

* fix(security): sanitize fields and tables when using nestTables

* chore: undone `nestTables` validation for `rowsAsArray`

Vulnerable code:
    fieldName = helpers.srcEscape(fields[i].name);

    if (helpers.privateObjectProps.has(fields[i].name)) {
      throw new Error(
        `The field name (${fieldName}) can't be the same as an object's private property.`,
      );
    }

    parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
      lvalue = `result[${helpers.srcEscape(
        fields[i].table + options.nestTables + fields[i].name,
      )}]`;
      tableName = helpers.srcEscape(fields[i].table);

      parserFn(`result[${helpers.srcEscape(resultTablesArray[i])}] = {};`);
    fieldName = helpers.srcEscape(fields[i].name);
    if (helpers.privateObjectProps.has(fields[i].name)) {
      throw new Error(
        `The field name (${fieldName}) can't be the same as an object's private property.`,
      );
    }

    parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
      lvalue = `result[${helpers.srcEscape(
        fields[i].table + options.nestTables + fields[i].name,
      )}]`;
      lvalue = `result[${helpers.srcEscape(fields[i].table)}][${fieldName}]`;
import { srcEscape } from '../../../../lib/helpers.js';
  { name: prop },
      `The field name (${srcEscape(fields[0].name)}) can't be the same as an object's private property.`,
import { srcEscape } from '../../../../lib/helpers.js';
  { name: prop },
      `The field name (${srcEscape(fields[0].name)}) can't be the same as an object's private property.`,

Fixed code:

const fieldEscape = (field) => {
  if (privateObjectProps.has(field)) {
    throw new Error(
      `The field name (${field}) can't be the same as an object's private property.`,
    );
  }

  return srcEscape(field);
};

exports.fieldEscape = fieldEscape;
    fieldName = helpers.fieldEscape(fields[i].name);
    // parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
      lvalue = `result[${helpers.fieldEscape(fields[i].table + options.nestTables + fields[i].name)}]`;
      tableName = helpers.fieldEscape(fields[i].table);

      parserFn(`result[${helpers.fieldEscape(resultTablesArray[i])}] = {};`);
  let tableName = '';
    fieldName = helpers.fieldEscape(fields[i].name);
    // parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
      lvalue = `result[${helpers.fieldEscape(fields[i].table + options.nestTables + fields[i].name)}]`;
      tableName = helpers.fieldEscape(fields[i].table);

      parserFn(`if (!result[${tableName}]) result[${tableName}] = {};`);
      lvalue = `result[${tableName}][${fieldName}]`;
  { name: prop, table: '' },
      `The field name (${fields[0].name}) can't be the same as an object's private property.`,

blockedFields
  .map((fields) =>
    fields.map((field) => ({ ...field, name: field.name.slice(1) })),
  )
  .forEach((fields) => {
    try {
      getBinaryParser(fields, { nestTables: '_' }, {});
      assert.fail('An error was expected');
    } catch (error) {
      assert.strictEqual(
        error.message,
        `The field name (_${fields[0].name}) can't be the same as an object's private property.`,
        `Ensure safe _${fields[0].name} for nestTables as string`,
      );
    }
  });

blockedFields
  .map((fields) =>
    fields.map((field) => ({ ...field, name: '', table: field.name })),
  )
  .forEach((fields) => {
    try {
      getBinaryParser(fields, { nestTables: true }, {});
      assert.fail('An error was expected');
    } catch (error) {
      assert.strictEqual(
        error.message,
        `The field name (${fields[0].table}) can't be the same as an object's private property.`,
        `Ensure safe ${fields[0].table} for nestTables as true`,
      );
    }
  });
  { name: prop, table: '' },
      `The field name (${fields[0].name}) can't be the same as an object's private property.`,

blockedFields
  .map((fields) =>
    fields.map((field) => ({ ...field, name: field.name.slice(1) })),
  )
  .forEach((fields) => {
    try {
      TextRowParser(fields, { nestTables: '_' }, {});
      assert.fail('An error was expected');
    } catch (error) {
      assert.strictEqual(
        error.message,
        `The field name (_${fields[0].name}) can't be the same as an object's private property.`,
        `Ensure safe _${fields[0].name} for nestTables as string`,
      );
    }
  });

blockedFields
  .map((fields) =>
    fields.map((field) => ({ ...field, name: '', table: field.name })),
  )
  .forEach((fields) => {
    try {
      TextRowParser(fields, { nestTables: true }, {});
      assert.fail('An error was expected');
    } catch (error) {
      assert.strictEqual(
        error.message,
        `The field name (${fields[0].table}) can't be the same as an object's private property.`,
        `Ensure safe ${fields[0].table} for nestTables as true`,
      );
    }
  });