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.
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.
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.
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.
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.
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.
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
.
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.
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.
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#!/.
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.