CyberChef is a tool for transforming input content to get an output using "recipes" defined by a user. It supports JSONPath lookups client side using a JavaScript library. JSONPath-Plus 9.0.0 has multiple vulnerabilities that may or may not be disclosed (at least one of them is, the other one looks like it got patched but doesn't have a specific disclosure). Blog post coming soon on a harrowing tale of RCE and XSS leading to CVE assignments and bounties after auditing a couple of JavaScript JSONPath library implementations.
Here's a stubby scrap that glosses over certain details and technical explanations about how I traversed through the following contexts:
Without going into great detail about this, as my upcoming larger post on the whole story of everything JavaScript JSONPath library vulnerability related is still in the works and disclosures are still pending, JSONPath-Plus 9.0.0, in a browser context, uses some AST generator/evaluator code to execute JSONPath expressions. What's a JSONPath?
It's like an xpath but for JSON.
We can look up the value of test.cool with the JSONPath $.test.cool
.
JSONPaths also support expressions for filtering arrays. For example, if we had an object like the following:
We could filter only objects in the test
array with a < 1
with a JSONPath like $.test[?(@.a < 1)]
Where this gets interesting is that there was no formal standard for this until February 2024. The libraries that implemented this in JavaScript wanted to support full JS functionality for these expressions with very poor attempts at doing this safely.
In the case of JSONPath-Plus 9.0.0 in the DOM context, as used by CyberChef, an AST parser and evaluator is used that is intended to prevent full on JS execution. It can be seen here.
The long and short of it is that this can be trivially escaped with a call to the result of calling the Function function from the constructor of a function type object in the AST evaluator. I am avoiding getting into the details around why this works with this post, so we will just accept as a fact that the following JSONPath leads to a breakout of this AST evaluator and full on JavaScript evaulation similar to calling Function("payload")()
.
$..[?((''.sub.constructor('console.log`1`')()))]
With this, we can build out a link like the following and achieve cross site scripting in CyberChef, however, it is only in the context of a Web Worker and we do not have access to the DOM.
We can see that the console.log`1337` payload gets executed on the page. Again, this is in the context of a Web Worker, so we have no access to the DOM. We cannot access things like document, or window for the DOM which limits the impact of this vulnerability as it stands. We can make on origin credentialed fetch requests, which for some application is enough to show tremendous impact, but I wasn't happy with this and wanted to see if I could traverse from this execution context to the main window.
Web Workers communicate back to the window that created them using postMessage listeners, allowing for potential bidirectional message driven communication between the two contexts. It was clear that the CyberChef worker that performs the recipe evaluation needed to be able to return its result back to the main window that created it so the output could be eventually rendered on the screen.
I spent a couple hours reading relevant code in the CyberChef repo that is used to create and talk to these Web Workers that evaluate the recipe and send back an output, and worked out a code path that would lead to being able to send a source over the postMessage bridge that the Web Worker uses and lead to an innerHTML setting sink.
Essentially, certain recipe types support HTML outputs. The main window context decides which type to use depending on the type of the input recipe. In the case of JSONPath evaluations, the content type is sadly not HTML. However, the message type for sending a completed "bake" of the recipe step back to the worker allows us to include which input step index the "bake" result is for. This allows us to add an additional step to our recipe at the end that uses an HTML output, and use our Web Worker XSS to send a bogus message to the DOM claiming to have completed a bake for it, allowing us to include arbitrary HTML in the recipe output view.
By adding an "Extract Files" step in our recipe after the "JSONPath Expression" step, and using a more advanced payload that executes in the context of the Web Worker, we can break out of the Web Worker and get the DOM to render HTML we control, allowing us to get XSS in the DOM.
To do this, we can use the following payload embeded in a wrapper to decode and execute it in the Web Worker context.
This payload sends a bogus message over the postMessage bridge to the main window from the web worker and is interpreted as if it is a response from the "Extract Files" step, leading to the result getting rendered as HTML. The result includes an additional javascript:
schema iframe with our JS we want to execute in the main window, traversing our execution from the Web Worker to the main window where we can now do stuff like access the DOM and read non HTTP only cookies.
A final payload that pops an alert from the DOM looks like the following: