VYPR
Critical severityOSV Advisory· Published Oct 28, 2019· Updated Aug 4, 2024

CVE-2019-10748

CVE-2019-10748

Description

Sequelize all versions prior to 3.35.1, 4.44.3, and 5.8.11 are vulnerable to SQL Injection due to JSON path keys not being properly escaped for the MySQL/MariaDB dialects.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Sequelize prior to versions 3.35.1, 4.44.3, and 5.8.11 allows SQL injection via unescaped JSON path keys in MySQL/MariaDB dialects.

Vulnerability

Overview Sequelize, a Node.js ORM, is vulnerable to SQL injection in the MySQL/MariaDB dialects due to improper escaping of JSON path keys [1][2]. The bug resides in the query generator's handling of JSON path expressions, where path segments are concatenated into a string without sanitization, allowing an attacker to inject arbitrary SQL [2].

Exploitation

An attacker can exploit this by supplying a crafted JSON object in a where clause. As demonstrated in a proof-of-concept by Snyk, using a key containing SQL syntax like 'a')) AS DECIMAL) = 1 UNION SELECT VERSION(); -- can execute arbitrary SQL commands [2]. No authentication is required if the application exposes such queries to user input.

Impact

Successful exploitation allows an attacker to execute arbitrary SQL statements, potentially leading to data exfiltration, modification, or deletion. The CVSS score is not yet provided by NVD [1], but the severity is high.

Mitigation

The vulnerability is fixed in Sequelize versions 3.35.1, 4.44.3, and 5.8.11 or higher [2]. Users should upgrade immediately. No workaround is documented.

AI Insight generated on May 21, 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
sequelizenpm
< 3.35.13.35.1
sequelizenpm
>= 4.0.0, < 4.44.34.44.3
sequelizenpm
>= 5.0.0, < 5.8.115.8.11

Affected products

2

Patches

1
a72a3f5

fix(mariadb): properly escape json path key (#11089)

https://github.com/sequelize/sequelizeSushantJun 21, 2019via ghsa
5 files changed · +34 21
  • lib/dialects/abstract/query-generator.js+4 4 modified
    @@ -1053,12 +1053,12 @@ class QueryGenerator {
              * https://bugs.mysql.com/bug.php?id=81896
              */
             paths = paths.map(subPath => Utils.addTicks(subPath, '"'));
    -        pathStr = ['$'].concat(paths).join('.');
    -        return `(${quotedColumn}->>'${pathStr}')`;
    +        pathStr = this.escape(['$'].concat(paths).join('.'));
    +        return `(${quotedColumn}->>${pathStr})`;
     
           case 'mariadb':
    -        pathStr = ['$'].concat(paths).join('.');
    -        return `json_unquote(json_extract(${quotedColumn},'${pathStr}'))`;
    +        pathStr = this.escape(['$'].concat(paths).join('.'));
    +        return `json_unquote(json_extract(${quotedColumn},${pathStr}))`;
     
           case 'sqlite':
             pathStr = this.escape(['$']
    
  • package.json+3 3 modified
    @@ -72,16 +72,16 @@
         "lint-staged": "^8.1.5",
         "mariadb": "^2.0.3",
         "markdownlint-cli": "^0.14.1",
    -    "mocha": "^6.0.2",
    +    "mocha": "6.0.2",
         "mysql2": "^1.6.5",
         "nyc": "^13.3.0",
         "pg": "^7.8.1",
         "pg-hstore": "^2.x",
         "pg-types": "^2.0.0",
         "rimraf": "^2.6.3",
         "semantic-release": "^15.13.3",
    -    "sinon": "^7.2.6",
    -    "sinon-chai": "^3.2.0",
    +    "sinon": "7.2.6",
    +    "sinon-chai": "3.2.0",
         "sqlite3": "^4.0.6",
         "tedious": "^6.0.0",
         "typescript": "^3.3.3333"
    
  • test/integration/include/findAndCountAll.test.js+1 0 modified
    @@ -73,6 +73,7 @@ describe(Support.getTestDialectTeaser('Include'), () => {
             });
           });
         });
    +
         it('should be able to include a required model. Result rows should match count', function() {
           const User = this.sequelize.define('User', { name: DataTypes.STRING(40) }, { paranoid: true }),
             SomeConnection = this.sequelize.define('SomeConnection', {
    
  • test/integration/model/json.test.js+12 0 modified
    @@ -685,6 +685,18 @@ describe(Support.getTestDialectTeaser('Model'), () => {
               });
             });
     
    +        it('should properly escape path keys', function() {
    +          return this.Model.findAll({
    +            raw: true,
    +            attributes: ['id'],
    +            where: {
    +              data: {
    +                "a')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- ": 1
    +              }
    +            }
    +          });
    +        });
    +
             it('should properly escape the single quotes in array', function() {
               return this.Model.create({
                 data: {
    
  • test/unit/sql/where.test.js+14 14 modified
    @@ -851,7 +851,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               prefix: 'User'
             }, {
               mariadb: "json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value'",
    -          mysql: "(`User`.`data`->>'$.\"nested\".\"attribute\"') = 'value'",
    +          mysql: "(`User`.`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value'",
               postgres: "(\"User\".\"data\"#>>'{nested,attribute}') = 'value'",
               sqlite: "json_extract(`User`.`data`, '$.nested.attribute') = 'value'"
             });
    @@ -866,7 +866,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested')) AS DECIMAL) IN (1, 2)",
    -          mysql: "CAST((`data`->>'$.\"nested\"') AS DECIMAL) IN (1, 2)",
    +          mysql: "CAST((`data`->>'$.\\\"nested\\\"') AS DECIMAL) IN (1, 2)",
               postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) IN (1, 2)",
               sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) IN (1, 2)"
             });
    @@ -881,7 +881,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested')) AS DECIMAL) BETWEEN 1 AND 2",
    -          mysql: "CAST((`data`->>'$.\"nested\"') AS DECIMAL) BETWEEN 1 AND 2",
    +          mysql: "CAST((`data`->>'$.\\\"nested\\\"') AS DECIMAL) BETWEEN 1 AND 2",
               postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) BETWEEN 1 AND 2",
               sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) BETWEEN 1 AND 2"
             });
    @@ -900,7 +900,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, { tableName: 'User' }))
             }, {
               mariadb: "(json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value' AND json_unquote(json_extract(`User`.`data`,'$.nested.prop')) != 'None')",
    -          mysql: "((`User`.`data`->>'$.\"nested\".\"attribute\"') = 'value' AND (`User`.`data`->>'$.\"nested\".\"prop\"') != 'None')",
    +          mysql: "((`User`.`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value' AND (`User`.`data`->>'$.\\\"nested\\\".\\\"prop\\\"') != 'None')",
               postgres: "((\"User\".\"data\"#>>'{nested,attribute}') = 'value' AND (\"User\".\"data\"#>>'{nested,prop}') != 'None')",
               sqlite: "(json_extract(`User`.`data`, '$.nested.attribute') = 'value' AND json_extract(`User`.`data`, '$.nested.prop') != 'None')"
             });
    @@ -919,7 +919,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               prefix: 'User'
             }, {
               mariadb: "(json_unquote(json_extract(`User`.`data`,'$.name.last')) = 'Simpson' AND json_unquote(json_extract(`User`.`data`,'$.employment')) != 'None')",
    -          mysql: "((`User`.`data`->>'$.\"name\".\"last\"') = 'Simpson' AND (`User`.`data`->>'$.\"employment\"') != 'None')",
    +          mysql: "((`User`.`data`->>'$.\\\"name\\\".\\\"last\\\"') = 'Simpson' AND (`User`.`data`->>'$.\\\"employment\\\"') != 'None')",
               postgres: "((\"User\".\"data\"#>>'{name,last}') = 'Simpson' AND (\"User\".\"data\"#>>'{employment}') != 'None')",
               sqlite: "(json_extract(`User`.`data`, '$.name.last') = 'Simpson' AND json_extract(`User`.`data`, '$.employment') != 'None')"
             });
    @@ -933,7 +933,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: "(CAST(json_unquote(json_extract(`data`,'$.price')) AS DECIMAL) = 5 AND json_unquote(json_extract(`data`,'$.name')) = 'Product')",
    -          mysql: "(CAST((`data`->>'$.\"price\"') AS DECIMAL) = 5 AND (`data`->>'$.\"name\"') = 'Product')",
    +          mysql: "(CAST((`data`->>'$.\\\"price\\\"') AS DECIMAL) = 5 AND (`data`->>'$.\\\"name\\\"') = 'Product')",
               postgres: "(CAST((\"data\"#>>'{price}') AS DOUBLE PRECISION) = 5 AND (\"data\"#>>'{name}') = 'Product')",
               sqlite: "(CAST(json_extract(`data`, '$.price') AS DOUBLE PRECISION) = 5 AND json_extract(`data`, '$.name') = 'Product')"
             });
    @@ -948,7 +948,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: "json_unquote(json_extract(`data`,'$.nested.attribute')) = 'value'",
    -          mysql: "(`data`->>'$.\"nested\".\"attribute\"') = 'value'",
    +          mysql: "(`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value'",
               postgres: "(\"data\"#>>'{nested,attribute}') = 'value'",
               sqlite: "json_extract(`data`, '$.nested.attribute') = 'value'"
             });
    @@ -963,7 +963,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) = 4",
    -          mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) = 4",
    +          mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) = 4",
               postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) = 4",
               sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) = 4"
             });
    @@ -980,7 +980,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) IN (3, 7)",
    -          mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) IN (3, 7)",
    +          mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) IN (3, 7)",
               postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) IN (3, 7)",
               sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) IN (3, 7)"
             });
    @@ -997,7 +997,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) > 2",
    -          mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) > 2",
    +          mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) > 2",
               postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) > 2",
               sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) > 2"
             });
    @@ -1014,7 +1014,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) > 2",
    -          mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) > 2",
    +          mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) > 2",
               postgres: "CAST((\"data\"#>>'{nested,attribute}') AS INTEGER) > 2",
               sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS INTEGER) > 2"
             });
    @@ -1032,7 +1032,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: `CAST(json_unquote(json_extract(\`data\`,'$.nested.attribute')) AS DATETIME) > ${sql.escape(dt)}`,
    -          mysql: `CAST((\`data\`->>'$."nested"."attribute"') AS DATETIME) > ${sql.escape(dt)}`,
    +          mysql: `CAST((\`data\`->>'$.\\"nested\\".\\"attribute\\"') AS DATETIME) > ${sql.escape(dt)}`,
               postgres: `CAST(("data"#>>'{nested,attribute}') AS TIMESTAMPTZ) > ${sql.escape(dt)}`,
               sqlite: `json_extract(\`data\`, '$.nested.attribute') > ${sql.escape(dt.toISOString())}`
             });
    @@ -1047,7 +1047,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: "json_unquote(json_extract(`data`,'$.nested.attribute')) = 'true'",
    -          mysql: "(`data`->>'$.\"nested\".\"attribute\"') = 'true'",
    +          mysql: "(`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'true'",
               postgres: "CAST((\"data\"#>>'{nested,attribute}') AS BOOLEAN) = true",
               sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS BOOLEAN) = 1"
             });
    @@ -1064,7 +1064,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
               }
             }, {
               mariadb: "json_unquote(json_extract(`meta_data`,'$.nested.attribute')) = 'value'",
    -          mysql: "(`meta_data`->>'$.\"nested\".\"attribute\"') = 'value'",
    +          mysql: "(`meta_data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value'",
               postgres: "(\"meta_data\"#>>'{nested,attribute}') = 'value'",
               sqlite: "json_extract(`meta_data`, '$.nested.attribute') = 'value'"
             });
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.