Talk-A-Blog: Apache Struts2 CVE-2023-50164, File Upload Vulnerability Analysis

A new vulnerability in Apache Struts has emerged! Follow us as we take a new twist on reviewing a vulnerability writeup.



December 12, 2023

In order to see CVE-2023-50164 in the wild, I expect that in the coming weeks, we will see research into vendor and product specific implementations leveraging Apache Struts2 in order to determine exactly what path must be traversed to in order to drop a web shell so that it can be called remotely through a public interface over the defined routes.

This analysis aided us in producing GreyNoise tag for CVE-2023-50164.

At GreyNoise we’re exploring new avenues to providing timely and accurate vulnerability information. As part of that process we wanted to run an exercise utilizing AI powered technologies in a collaborative environment. In this case, we’re recorded a Zoom screen-share as Remy read through a vulnerability write-up on Apache Struts2 CVE-2023-50164, calling out the aspects of the vulnerability that may not be immediately apparent.

Initially we attempted to use Zoom’s AI Summary feature, which failed quite miserably to communicate any of the necessary nuance. As follows is:

and after-the-fact commentary

… all cenetered aroud a vulnerbility write-up located at:

Let’s dig in!

Today we’re reading a vulnerability analysis write-up for Apache Struts2, File Upload Vulnerability Analysis, CVE-2023-50164.

They give a brief overview and brief description of Apache Struts 2 being an open source Java web application development framework designed to help yada, yada, yada. Maintainable, scalable enterprise class web application. Apache Struts 2 on its own is a development framework. While the repository may provide demo applications, at its heart, you will find Apache Struts embedded in various enterprise grade applications and business use cases.

Historically, we’ve seen this pop up in Atlassian products such as Confluence or Jira, as well as many other business use-case software. They begin by researching about how Apache Struts2, being open source software, allowed them to identify how the vulnerability may occur. I’m familiar with Java vulnerability analysis, more than likely what this is going to be is shortest path in order to get remote code execution would be to ultimately drop some sort of JSP file on the file system and then do a follow-on HTTP request in order to call that JSP file, in this case, being a web shell, leading to remote code execution.

So a two-step process, and those prerequisites are more than likely what this person is going to try to build off of.

They start by scoping the vulnerability to version 6.3.0 in order to do testing. For key knowledge points, they start talking about Apache Struts “ActionSupport”.

For many web development frameworks, actions, as the verb might imply, does something with content. In this case, they’re defining three attributes, upload, which represents the uploaded file, fileName which represents the uploaded file name, and file Content-Type represents the uploaded file type.

In this case, the file Content-Type, I expect that there may be filters on the server side, and it’s not immediately clear whether this is some sort of MIME type sniffing done on the client side, which may misrepresent the trust between the client and the server, or if this is some sort of logical bypass on the backend in terms of how it’s structured.

If I was an attacker, I would start hammering on either the uploaded fileName and the Content-Type and how it handles that chain of events, especially for larger multi-part files.

The premise regarding a Struts file upload, they’re going to define the happy path here. We see them touching on the default configuration, which many developers, when implementing a framework, will not change unless absolutely necessary.

Here we see the default properties calling out that the struts multipart save directory has an equal sign, but no defined value. In this case, I expect that there will be a default, and that’s probably what they’re going to dig into next. We see that they’re disassembling the getSaveDir, which we can see falls back to if save directory equals an empty string.

It will get the attribute for javax.servlet.context.tempdir. Due to the naming of this class, I expect that this is going to provision a temporary directory commonly used for scratch disk. In action item two, they call out that if configured, follow the path given lots of exclamation marks. I think there’s some translation error here, and they say “needs to be an upload interface, which is under certain conditions”.

Reading between the lines: Apache Struts2 is a development framework. The vulnerable code path is a feature of the development framework, but may not necessarily be enabled in every implementation using Apache Struts2. There may be differentiations between different vendors and products and whether or not they’re actually vulnerable, despite having a vulnerable version used.

The writer of this blog then dives into the build environment. They talk about how they approached it by actually building the source code so that they can modify the source code and debug it for viewing. This is a common pattern when using Java applications. There are also command line flags; You can commonly pass to the JVM runtime in order to do dynamic debugging. It all depends on really what tools you have available in order to facilitate the process of vulnerability research.

They then talk about making a modification to an action in order to mimic what an official action may look like. In this case, “official one”, I presume is referring to how an actual business enterprise application may use this feature of Apache Struts2.

They provide the source code for how they implemented that mimicking a real business use case. And then finally, in the struts file upload XML based definition, they define an action class for uploadAction with a method of upload, a name of upFile, and finally registering the path for that content at the. Route for demo.

  • /demoupload.jsp

So in any sort of PoC demoupload.jsp is not directly part of a vulnerable PoC. It is simply the defined route in which they’ve created the vulnerable code path where they can test upon it.

They then dive into the vulnerability analysis.

It appears here that they’re defining normal circumstances or the “happy path” so that they can start to understand where deviations from the “happy path” may occur.

They talk about under normal circumstances when you upload a file, the cached file in this case “cached” being the temporary directory will only exist for a few seconds before disappearing. This is a common use case for temporary directories across all the operating system where once the callees of a function call that write to the temporary directory have successfully completed that file is removed from temporary storage freeing up the scratch disk for other processes.

They call out that they adopt the default state entering here the storage path will be determined by default, it will go to the set multi-part upload directory method to execute. It’s made apparent to me that in terms of the conversation I had earlier regarding mistakenly trusting my Content-Type, it seems like the file Content-Type may actually be whether or not a file’s content and size can fit entirely within one POST request or requires a stream of multi-part form requests, and these are distinctly different in how a web application manages its state and oftentimes any parameters that are involved there can potentially be vulnerable to some sort of prototype pollution.

Again, I’m still focused on the directory traversal and how that can be achieved as that’s typically the path followed for remote code execution.

Here we see that they’re calling out the default state for when a multi path support is enabled where get saved directory is called. The getSaveDir() determines the determined path and generates a temp temporary file. What I take this to mean is rather than directly dumping a file into the temp directory so that they’re all just in one large lump likely there’s some sort of wrapper around this in order to generate. Either a unique identifier a good etc or some sort of directory structure with a unique ID so that it’s not just a flat file system where things dumped in the tempered temp directory can ultimately cause system performance. This is sort of a tiered folder structure that you see in a lot of systems design and it allows them to track what went where, even though it is ultimately a temp file.

Finally, structs will delete the file data that is delete the temporary file.

As the request is completed, they then begin talking about how they can do it patched it for parameters tests and how tests can be used in order to determine the nature of the vulnerability.

Tests in open source software are one of the best places to do vulnerability research in terms of well defined problems, not only do they allow you to determine A/B cases, but they also are pinned to an exact version in terms of when that test case would not be expected to be true.

Here they call out to test cases shouldGetBeCaseInsensitive and shouldAppendSameParamsIgnoringCase.

So this is touching on one of the key items of capitalization potentially being involved in the parameters pass to a multi part form handler. As a result, I expect that some constraints are not met within the Java web application, allowing for some sort of directory traversal. In this case, I’m going to call out to test cases shouldGetBeCaseInsensitive and shouldAppendSameParamsIgnoringCase.

So, in this case, I expect that some constraints are not met within the Java web application. So, in this case, I expect that some constraints are not met within the Java web application. So, in this case, I expect that some constraints are not met within the Java web application. So, in this case, I expect that some constraints are not met within the Java web application. So, in this case, I expect that some constraints are not met within the Java web application. So, in this case, I expect that some constraints are not met within the Java web application. So, in this case, I expect that some constraints are not met within the Java web application. So, in this case, I expect that some constraints are not met within the Java web application.

Great news everyone! AI isn’t gonna take our jobs!

and the previous versions were simply checking a parameter key, whereas now the current patch state adds someadditional constraints for to lowercase and equals ignore case.

In terms of exploitation at this point, we understand it to involve capitalization of parameters provided in a multi-form request. And what remains to be seen is how some sort of directory traversal is allowing them to write an arbitrary web shell.

Again, they validate the happy path using a set of normal data. In this defined web request here, we see that WebKit form boundary. We see parameters for Content-Disposition and name, in this case being upload in blue text with all lowercase letters, the file name, blue text, all lowercase letters, and a content type of text/plain. There is an implied \r\n\r\n for two new lines implied here with a follow on WebKit form boundary stating to the server that this file is completed uploading.

They investigate the values of the hash map used for debugging purposes and to determine whether or not that map has been populated correctly, and they decided to change the case of the parameter for name.

This is the name parameter, and they’ve capitalized the first letter U in Upload and determined what that causes downstream resulting in some sort of protoype pollution dependent on captilization and ordering of parameters. If order matters and you’re working with multi-part form request data in which parameters can be supplied at any WebKit form boundary, WebKit form boundaries are a stream, so if you’re able to modify constraints that were already evaluated in an earlier WebKit form boundary and overwrite them, you may be able to craft some sort of bypass, and that seems to be what they’re calling out here.

They’re highlighting a section of code that deals with escaping of path characters for file separators. A forward slash and a backslash, and whether or not those constraints are met. They state there is a restriction on file names. This will cause all past values carrying an escaped backslash to be deleted. So path normalization. In this case, the functionally named getCanonicalName is called many things in programming.

They start talking about prototype pollution part in detail, and we touched on prototype pollution, and this is not one of my strong suits, and they have a lot of unformatted text. I’m largely going to scroll right past that because I don’t think it’s immediately apparent why I should care.

We know the things that we have to play with, and ultimately, a fuzzer would probably be able to catch this pretty fast, which is how I would approach this problem. Burp Suite or similar would nail this in an instant.

They state “if it is larger than this part of code, if the size exceeds string error key stretch message upload error parameter too long, this error will pop up. Finally, prepare cleanup wrapped request will be executed. However, it becomes two files when generated, and it is divided into two files when adding item.add above. When your size exceeds, the exception handling will be performed. The best thing about supplementing is they can define the multi-part save directory.”

By causing an exception condition with a multi-part form request, it appears that they’re able to pollute the path which a multi-part upload is allocated on the disk, bypassing the constraints for path normalization. As a result, I expect that’s where they’re going to insert their directory traversal.

They’re doing some patch diffing using GitHub and diffing commits. Specifically, what the second picture does is to limit the caption parameter to prevent the ength from exceeding. I’m still not necessarily clear on what this means, but I imagine that this is going to become immediately apparent in their final vulnerability reproduction.

Here, we see, as expected, rather than just a starting WebKit form boundary, and an ending WebKit form boundary, we see that they’ve defined a request that has three WebKit form boundaries.

In the initial one, the Content-Disposition parameter is defined as form-data. The name is defined as a capitalized Upload. The file name is 1.txt with a Content-Type of text/plain an implied \r\n\r\n and malformed XML with an unmatched closing tag?. I don’t believe that this parameter for JPS with an invalid JSP ending is a capitalized upload. I don’t believe that this parameter for <jps> with an unmatched node is actually relevant. And I believe that this portion can actually be arbitrary content for a webshell.

Finally, that content is finished by the second WebKit form boundary. The injectable portion here, we see content disposition form data is redefined with a name of upload file name and a content for ../../shell.jsp. Ultimately, this is going to be the path traversal vulnerability where a webshell is written to the file system that can then be remotely called. And again, they show this as a proof of concept actually being launched and show demonstration that shell.jsp as the file is written to the disk.

While they’ve proven that you can drop an arbitrary shell.jsp or webshell to an Apache Struts2 to web application, the additional constraints that the web application may have a must have a defined actions path in order to allow the sort of multi-part form file upload. Additionally, they’ve dropped the shell in a traversed directory.

The dropped shell.jsp file must be in a valid route that can be remotely reached by an attacker in order to be triggered. And this will vary from web application to web application. In order to see this in the wild, I expect that in the coming weeks, we will see research into vendor and product specific implementations leveraging Apache Struts2 in order to determine exactly what path must be traversed to in order to drop a web shell so that it can be called remotely through a public interface over the defined routes.

As a result, this is something that may affect major vendors who are using Apache Struts2, but is definitely noteworthy in terms of knowing you use in your code base and how you use it. I’m very interested to see what’s going to happen in the coming weeks, and there will be several different variations depending on vendor product and how it is implemented on the server side, depending on the business use logic.