CVE-2024-21511: Insufficient Information (NVD-CWE-noinfo)
value: Versions of the package mysql2 before 3.9.7 are vulnerable to Arbitrary Code Injection due to improper sanitization of the timezone parameter in the readCodeFor function by calling a native MySQL Server date/time function.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-23T05:15Z
Repository: https://github.com/sidorares/node-mysql2
Language: JavaScript
Files changed: lib/parsers/binary_parser.js, lib/parsers/text_parser.js, test/esm/unit/parsers/timezone-binary-sanitization.test.mjs, test/esm/unit/parsers/timezone-text-sanitization.test.mjs
Commit date: 2024-04-21 20:59:27 +1000
Commit message: fix(security): sanitize timezone parameter value to prevent code injection (#2608)

* fix(security): sanitize timezone parameter value to prevent code injection. Discovered by zhaoyudi (Nebulalab）
Diff: diff --git a/lib/parsers/binary_parser.js b/lib/parsers/binary_parser.js
index 1083204f..9d53d8c1 100644
--- a/lib/parsers/binary_parser.js
+++ b/lib/parsers/binary_parser.js
@@ -40,13 +40,13 @@ function readCodeFor(field, config, options, fieldNum) {
     case Types.DATE:
     case Types.DATETIME:
     case Types.TIMESTAMP:
     case Types.NEWDATE:
       if (helpers.typeMatch(field.columnType, dateStrings, Types)) {
-        return `packet.readDateTimeString(${field.decimals});`;
+        return `packet.readDateTimeString(${parseInt(field.decimals, 10)});`;
       }
-      return `packet.readDateTime('${timezone}');`;
+      return `packet.readDateTime(${helpers.srcEscape(timezone)});`;
     case Types.TIME:
       return 'packet.readTimeString()';
     case Types.DECIMAL:
     case Types.NEWDECIMAL:
       if (config.decimalNumbers) {
diff --git a/lib/parsers/text_parser.js b/lib/parsers/text_parser.js
index 7e465144..be89e771 100644
--- a/lib/parsers/text_parser.js
+++ b/lib/parsers/text_parser.js
@@ -46,17 +46,17 @@ function readCodeFor(type, charset, encodingExpr, config, options) {
       return 'packet.readLengthCodedString("ascii")';
     case Types.DATE:
       if (helpers.typeMatch(type, dateStrings, Types)) {
         return 'packet.readLengthCodedString("ascii")';
       }
-      return `packet.parseDate('${timezone}')`;
+      return `packet.parseDate(${helpers.srcEscape(timezone)})`;
     case Types.DATETIME:
     case Types.TIMESTAMP:
       if (helpers.typeMatch(type, dateStrings, Types)) {
         return 'packet.readLengthCodedString("ascii")';
       }
-      return `packet.parseDateTime('${timezone}')`;
+      return `packet.parseDateTime(${helpers.srcEscape(timezone)})`;
     case Types.TIME:
       return 'packet.readLengthCodedString("ascii")';
     case Types.GEOMETRY:
       return 'packet.parseGeometryValue()';
     case Types.JSON:
diff --git a/test/esm/unit/parsers/timezone-binary-sanitization.test.mjs b/test/esm/unit/parsers/timezone-binary-sanitization.test.mjs
new file mode 100644
index 00000000..03b2591a
--- /dev/null
+++ b/test/esm/unit/parsers/timezone-binary-sanitization.test.mjs
@@ -0,0 +1,24 @@
+import { describe, test, assert } from 'poku';
+import { createConnection, describeOptions } from '../../../common.test.cjs';
+
+const connection = createConnection().promise();
+
+describe('Binary Parser: timezone Sanitization', describeOptions);
+
+Promise.all([
+  test(async () => {
+    process.env.TEST_ENV_VALUE = 'secure';
+    await connection.execute({
+      sql: 'SELECT NOW()',
+      timezone: `'); process.env.TEST_ENV_VALUE = "not so much"; //`,
+    });
+
+    assert.strictEqual(
+      process.env.TEST_ENV_VALUE,
+      'secure',
+      'Timezone sanitization failed - code injection possible',
+    );
+  }),
+]).then(async () => {
+  await connection.end();
+});
diff --git a/test/esm/unit/parsers/timezone-text-sanitization.test.mjs b/test/esm/unit/parsers/timezone-text-sanitization.test.mjs
new file mode 100644
index 00000000..82ba9546
--- /dev/null
+++ b/test/esm/unit/parsers/timezone-text-sanitization.test.mjs
@@ -0,0 +1,24 @@
+import { describe, test, assert } from 'poku';
+import { createConnection, describeOptions } from '../../../common.test.cjs';
+
+const connection = createConnection().promise();
+
+describe('Text Parser: timezone Sanitization', describeOptions);
+
+Promise.all([
+  test(async () => {
+    process.env.TEST_ENV_VALUE = 'secure';
+    await connection.query({
+      sql: 'SELECT NOW()',
+      timezone: `'); process.env.TEST_ENV_VALUE = "not so much"; //`,
+    });
+
+    assert.strictEqual(
+      process.env.TEST_ENV_VALUE,
+      'secure',
+      'Timezone sanitization failed - code injection possible',
+    );
+  }),
+]).then(async () => {
+  await connection.end();
+});

Vulnerable code:         return `packet.readDateTimeString(${field.decimals});`;
      return `packet.readDateTime('${timezone}');`;
      return `packet.parseDate('${timezone}')`;
      return `packet.parseDateTime('${timezone}')`;
Fixed code:         return `packet.readDateTimeString(${parseInt(field.decimals, 10)});`;
      return `packet.readDateTime(${helpers.srcEscape(timezone)});`;
      return `packet.parseDate(${helpers.srcEscape(timezone)})`;
      return `packet.parseDateTime(${helpers.srcEscape(timezone)})`;
import { describe, test, assert } from 'poku';
import { createConnection, describeOptions } from '../../../common.test.cjs';

const connection = createConnection().promise();

describe('Binary Parser: timezone Sanitization', describeOptions);

Promise.all([
  test(async () => {
    process.env.TEST_ENV_VALUE = 'secure';
    await connection.execute({
      sql: 'SELECT NOW()',
      timezone: `'); process.env.TEST_ENV_VALUE = "not so much"; //`,
    });

    assert.strictEqual(
      process.env.TEST_ENV_VALUE,
      'secure',
      'Timezone sanitization failed - code injection possible',
    );
  }),
]).then(async () => {
  await connection.end();
});
import { describe, test, assert } from 'poku';
import { createConnection, describeOptions } from '../../../common.test.cjs';

const connection = createConnection().promise();

describe('Text Parser: timezone Sanitization', describeOptions);

Promise.all([
  test(async () => {
    process.env.TEST_ENV_VALUE = 'secure';
    await connection.query({
      sql: 'SELECT NOW()',
      timezone: `'); process.env.TEST_ENV_VALUE = "not so much"; //`,
    });

    assert.strictEqual(
      process.env.TEST_ENV_VALUE,
      'secure',
      'Timezone sanitization failed - code injection possible',
    );
  }),
]).then(async () => {
  await connection.end();
});