Let me start off this blog post with a quote from my compatriot George Santayana:
Those who do not learn history are doomed to repeat it.
This is true in many aspects of life, and appsec is not an exception. When dealing with security vulnerabilities we find ourselves repeating the same mistakes we (or others) made few years ago. The diversity in software bugs and flaws makes it really difficult to keep track of all new vulnerabilities being reported. Not only that, but in recent years, the overwhelming number of CVEs reported makes it really hard and time consuming to keep up to date with the most interesting or relevant ones.
With the humble goal to help the OSS community digest all these CVEs, we are starting a new series of blog posts and twitter threads in which a GitHub Security Lab researcher will write about any recent CVE of his choice. We are a diverse team, and hopefully this diversity will reflect on this series. We will be visiting various subjects, from memory corruption bugs, to high-level architectural flaws, and this will hopefully help us all better understand how to find and prevent vulnerabilities in the future.
If you are a software developer, this series may introduce you to vulnerable code patterns similar to code you have written in the past, or even to code you are currently working on. Even if you have never produced such vulnerable code, being introduced to the recurring themes behind many of these vulnerability patterns is helpful in your every day programming tasks. For example, the recurring theme of allowing unchecked user-controlled data to flow into potentially sensitive operations such as SQL statements or LDAP queries is the root cause for many vulnerabilities. If you are in appsec, you may learn about new vulnerabilities to look for in other projects, or just learn new tricks to exploit vulnerabilities you were already familiar with.
Whatever your focus may be, from developer to security professional, we hope this is an interesting read for you.
CVE-2020-5398: RFD attack via Content-Disposition header sourced from request input by Spring MVC or Spring WebFlux Application
When I checked for the latest 2020 CVEs to start off this series, one caught my eye: CVE-2020-5398. The reason for that is that it’s a Reflected File Download (RFD) vulnerability in Spring framework (MVC and WebFlux) and I remember having reported it some years ago in the same project. So I decided to do some research to find out if it was due to a bypass of the mitigations that were put in place at that time, or to a new vector I was not familiar with.
In Spring Framework, versions 5.2.x prior to 5.2.3, versions 5.1.x prior to 5.1.13, and versions 5.0.x prior to 5.0.16, an application is vulnerable to a reflected file download (RFD) attack when it sets a “Content-Disposition” header in the response where the filename attribute is derived from user supplied input.
Reflected File Download
First things first, for those of you who are not familiar with Reflected File Downloads (RFD), it’s a breed of bugs discovered by Oren Hafif from TrustWave back in 2014. You can find a blog post and a white paper explaining all nuances and details, but in a short sentence we could say an application is vulnerable when:
- The application reflects user-controllable data
- The application allows attackers to treat the server response as a file download
- Attackers can prevent the
content-dispositionheader from setting a file name, and at the same time can influence the URL in such a way that the browser will derive an attacker-controlled file name …
- … or can control the
content-dispositionfile name and extension
When the above conditions are met, an attacker is able to craft a URL link that will force a download (even for non-downloadable content types) and control part of the file content along with the file name and extension.
If a victim clicks on this link, they will be presented with a download dialog from a trusted site which if executed, will write attacker controlled data to an attacker controlled file type due to file suffix control. This can result in arbitrary commands being executed on the victim’s system.
Back to 2015
5 years ago I reported this issue was affecting Spring framework since attackers were able to craft such links by abusing Spring content-negotiation. For example, an attacker was able to force a JSON response to be downloaded with an attacker controllable name: Since the REST API does not generate a content disposition header for
application/json responses, the browser fell back to the requested URL to infer the file name assigned to the JSON response.
An attacker could prepare a click-bait trap for the victim in the form of:
<a href=”https://<trusted-server>.com/api/users/<attacker_id>.cmd" download> Click me, Im a dolphin </a>
- The attacker was able to control part of the JSON response by pointing to this own profile where we was able to inject a payload such as
- The anchor’s download attribute will force the response to be treated as a file download.
- Since there is no
content-dispositionheader, the browser is left on its own to choose a name for the downloaded file.
- Spring’s content negotiation allows URLs to contain
.<content-type>suffices instead of using the
- Non-Recognised content-types will default to JSON, but the URL will still be valid.
- When clicked, the browser will download a file called
- Based on victim’s trust on
<trusted_server>, the user may accept any warning and open the downloaded file, enabling the attacker to run arbitrary commands.
The way Spring fixed the issue, was by forcing a static (
content-disposition header for any non-registered content-type. This way, in the above example, the attacker can’t control the file name and the response is downloaded as a harmless
Back to the present
In 2015 the problem was related to user-controlled file names for responses with no
content-disposition header. This time the problem is related to a lack of escaping when creating
content-disposition headers, that may allow an attacker to bypass a “hardcoded” extension and use an arbitrary one.
For example, in the following code, users may control the file name but the extension should always be
ContentDisposition contentDisposition = ContentDisposition.builder("attachment").filename(fileName + ".txt").build(); HttpHeaders headers = new HttpHeaders(); headers.setContentDisposition(contentDisposition);
However, due to incorrect escaping of the quote character
", an attacker who controls
fileName could send the following file name (notice the final
This will generate the following
Content-Disposition: attachment; filename="secure_install.cmd";.txt"
Therefore, if the attacker is able to control part of the contents of the file, it may craft a malicious script that may get executed by the victim because of the trust that the victim has placed in the accessed server.
The fix consists of escaping quotes in the filename, preventing attackers from closing the filename attribute. Once the fix is applied, if an attacker tries to send the same filename, the
content-disposition header will look like:
Content-Disposition: attachment; filename="secure_install.cmd\";.txt"
This removes the risk for inadvertent command execution by properly retaining the
.txt file extension.
The Reflected File Download vulnerability pattern is not that commonly known but can be effectively prevented with some basic awareness of the corner cases that allow for it to manifest. We hope that this post provided you with all the insights you needed to prevent RFD bugs in your own code!