Proposal - Making JSON Logic Safe #39
Replies: 3 comments 19 replies
-
|
My thoughts:
|
Beta Was this translation helpful? Give feedback.
-
|
Here a few inputs (food for thoughts):
I do not think the max length limitation is bad, more than it should move to the specific operator configuration and stay unrelated to the security as it is when you code in any language since once "compiled"/interpreted, JSON-Logic is a runtime as any language. As a summary security doesn't really comes from limiting the runtime but 1) ensuring it can be validated upfront and 2) at runtime as an harnessing and complement. |
Beta Was this translation helpful? Give feedback.
-
|
@TotalTechGeek Do you mean to impose If I had more time I'd be tempted to write a type system of sorts for evaluating the algorithmic complexity of a JSONLogic expression. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Background
Hey folks! It's been a while!
I've had some time to think, and I believe I stumbled across some issues with the JSON Logic Specification that undermines its goals.
The goals of JSON Logic, shared from the first section of its website -- https://jsonlogic.com/:
More recently, we had a discussion about how operators needed to be definable in such a way where they could run in Linear Time (while we did not harden it to require linear time execution).
See: https://github.com/orgs/json-logic/discussions/24
The Flaws
I believe that the iterators in JSON Logic open vulnerabilities in contexts where the rules come from an untrusted source,
I've stumbled across two types of issues, and I believe they should be fairly "simple" to mitigate. Both of these rules crash every implementation I've tested against.
The first classification of issue is one that allows for exponential growth in data, which could lead to DOS in either memory or compute, if the output were passed into another operator.
Exponential Growth in Reduce
Here is such a rule that should be compatible with every implementation of JSON Logic.
{ "reduce": [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { "merge": [ { "var": "accumulator" }, { "var": "accumulator" }, { "var": "accumulator" }, { "var": "accumulator" }, { "var": "accumulator" }, { "var": "accumulator" }, { "var": "current" } ] }, [] ] }Because the accumulator is able to self-reference between executions and be used multiple times, you end up with exponential growth during the iterations (recursive expansion).
Nested Outer Iterator Increase The Degree of Time Complexity
The second classification is more obvious, and one I believe folks were more generally aware of,
{ "map": [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { "map": [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { "map": [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { "map": [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { "map": [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { "map": [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { "map": [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { "map": [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { "map": [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 0 ] } ] } ] } ] } ] } ] } ] } ] } ] }By nesting iterator statements in the outer expression, each nested iterator can increase the time complexity another degree, (n^1 becomes n^2 becomes n^3 ...).
This can also be used to create a denial of service from compute.
My Take
I believe that it should be a goal of the JSON Logic project to provide a safe runtime to users, even if the rules come from an untrusted source, and that Core operators should be defined with reasonable constraints to protect that safety for users out of the box.
However, with JSON Logic being a powerful AST, I believe we should recommend that implementations provide configurabilty for some of these constraints for trusted contexts.
Recommendations
I think we should aim to keep the changes relatively simple and straightforward, where possible. They also need to be compatible with previously approved tests.
So:
reduceMUST throw ifarray/objectis used as either a defaultValue or returned from the expression.My recommendation for implementations is to make this configurable, but out of the box this should be disabled to mitigate exponential growth.
This allows
reduceto be used to aggregate scalar values (and strings) while not allowing it to aggregate complex structures by default.I feel like most real-world use-cases that would need to generate structures in
reducein an untrusted context in linear time could be managed with{ merge: { map: [...] } }I feel this constraint helps mitigate other weird attacks where people could make awkwardly nested data structures to avoid the other constraints described below.
maxStringLengthshould be a specified configuration setting.maxArrayLengthshould be a specified configuration setting.catmust honormaxStringLengthand throw where applicable.mergemust honormaxArrayLengthand throw where applicablemaxArrayLengthprior to iterating.maxIteratorDepthshould be a specified configuration setting, with a default of0.Note that depth is only increased in the body of the iterator, for example, you can still do
{ map: [{ filter: ... }, ...] }Beta Was this translation helpful? Give feedback.
All reactions