CVE-2024-27307: Insufficient Information (NVD-CWE-noinfo)
value: JSONata is a JSON query and transformation language. Starting in version 1.4.0 and prior to version 1.8.7 and 2.0.4, a malicious expression can use the transform operator to override properties on the `Object` constructor and prototype. This may lead to denial of service, remote code execution or other unexpected behavior in applications that evaluate user-provided JSONata expressions. This issue has been fixed in JSONata versions 1.8.7 and 2.0.4. Applications that evaluate user-provided expressions should update ASAP to prevent exploitation. As a workaround, one may apply the patch manually.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-06T20:15Z
Repository: https://github.com/jsonata-js/jsonata
Language: JavaScript
Files changed: src/jsonata.js, test/implementation-tests.js
Commit date: 2024-02-29 16:43:10 +0000
Commit message: Prevent access to __proto__

Signed-off-by: andrew-coleman <andrew_coleman@uk.ibm.com>

Diff: diff --git a/src/jsonata.js b/src/jsonata.js
index 8b4a68c..199b7df 100644
--- a/src/jsonata.js
+++ b/src/jsonata.js
@@ -1291,10 +1291,17 @@ var jsonata = (function() {
                 if(!Array.isArray(matches)) {
                     matches = [matches];
                 }
                 for(var ii = 0; ii < matches.length; ii++) {
                     var match = matches[ii];
+                    if (match && match.isPrototypeOf(result)) {
+                        throw {
+                            code: "D1010",
+                            stack: (new Error()).stack,
+                            position: expr.position
+                        };
+                    }
                     // evaluate the update value for each match
                     var update = await evaluate(expr.update, match, environment);
                     // update must be an object
                     var updateType = typeof update;
                     if(updateType !== 'undefined') {
@@ -1537,11 +1544,11 @@ var jsonata = (function() {
         } catch(err) {
             if(proc) {
                 if (typeof err.token == 'undefined' && typeof proc.token !== 'undefined') {
                     err.token = proc.token;
                 }
-                err.position = proc.position;
+                err.position = proc.position || err.position;
             }
             throw err;
         }
         return result;
     }
@@ -1970,10 +1977,11 @@ var jsonata = (function() {
         "T1005": "Attempted to invoke a non-function. Did you mean ${{{token}}}?",
         "T1006": "Attempted to invoke a non-function",
         "T1007": "Attempted to partially apply a non-function. Did you mean ${{{token}}}?",
         "T1008": "Attempted to partially apply a non-function",
         "D1009": "Multiple key definitions evaluate to same key: {{value}}",
+        "D1010": "Attempted to access the Javascript object prototype", // Javascript specific 
         "T1010": "The matcher function argument passed to function {{token}} does not return the correct object structure",
         "T2001": "The left side of the {{token}} operator must evaluate to a number",
         "T2002": "The right side of the {{token}} operator must evaluate to a number",
         "T2003": "The left side of the range operator (..) must evaluate to an integer",
         "T2004": "The right side of the range operator (..) must evaluate to an integer",
diff --git a/test/implementation-tests.js b/test/implementation-tests.js
index 1abc214..ace6efe 100644
--- a/test/implementation-tests.js
+++ b/test/implementation-tests.js
@@ -953,10 +953,30 @@ describe("Tests that are specific to a Javascript runtime", () => {
                     index: 1,
                 });
             });
         });
     });
+    describe("Expressions that attempt to pollute the object prototype", function() {
+        it("should throw an error with __proto__", async function() {
+            const expr = jsonata('{} ~> | __proto__ | {"is_admin": true} |');
+            expect(
+                expr.evaluate()
+            ).to.eventually.be.rejected.to.deep.contain({
+                position: 7,
+                code: "D1010",
+            });
+        });
+        it("should throw an error with __lookupGetter__", async function() {
+            const expr = jsonata('{} ~> | __lookupGetter__("__proto__")() | {"is_admin": true} |');
+            expect(
+                expr.evaluate()
+            ).to.eventually.be.rejected.to.deep.contain({
+                position: 7,
+                code: "D1010",
+            });
+        });
+    });
 });
 
 describe("Test that yield platform specific results", () => {
     // Platform specific
     describe("$sqrt(10) * $sqrt(10)", function() {

Vulnerable code:                 err.position = proc.position;
Fixed code:                     if (match && match.isPrototypeOf(result)) {
                        throw {
                            code: "D1010",
                            stack: (new Error()).stack,
                            position: expr.position
                        };
                    }
                err.position = proc.position || err.position;
        "D1010": "Attempted to access the Javascript object prototype", // Javascript specific 
    describe("Expressions that attempt to pollute the object prototype", function() {
        it("should throw an error with __proto__", async function() {
            const expr = jsonata('{} ~> | __proto__ | {"is_admin": true} |');
            expect(
                expr.evaluate()
            ).to.eventually.be.rejected.to.deep.contain({
                position: 7,
                code: "D1010",
            });
        });
        it("should throw an error with __lookupGetter__", async function() {
            const expr = jsonata('{} ~> | __lookupGetter__("__proto__")() | {"is_admin": true} |');
            expect(
                expr.evaluate()
            ).to.eventually.be.rejected.to.deep.contain({
                position: 7,
                code: "D1010",
            });
        });
    });