CVE-2024-53677 - Apache Struts File Upload Vulnerability leading to RCE

Hitesh PatraHitesh Patra
7 min read

Apache has announced a critical vulnerability affecting Apache Struts, a widely used Java-based web application framework by various organizations, including government agencies, e-commerce platforms, financial institutions, and airlines.

Apache publicly released an advisory recently stating,

File upload logic is flawed, and allows an attacker to enable paths with traversals - similar problem as reported in S2-066

An attacker can manipulate file upload params to enable paths traversal and under some circumstances this can lead to uploading a malicious file which can be used to perform Remote Code Execution.

Note: applications not using FileUploadInterceptor are safe.

Affected versions,

  • Struts 2.0.0 through Struts 2.3.37 (EOL)

  • Struts 2.5.0 through Struts 2.5.33 (EOL)

  • Struts 6.0.0 through Struts 6.3.0.2

It has been assigned as CVE-2024-53677 with a CVSS 4.0 scored as 9.5 - Critical

Vector: CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/S:N/AU:Y/R:A/V:C/RE:L/U:Red

What is the Vulnerability?

CVE-2024-53677 is a file upload vulnerability in Apache Struts 2, stemming from path traversal flaws in the deprecated File Upload Interceptor component. By exploiting this vulnerability, attackers can manipulate file upload parameters to navigate directories and upload malicious files. In certain conditions, these files may lead to remote code execution, enabling attackers to gain control over affected systems.

Applications not utilizing the File Upload Interceptor component remain unaffected by CVE-2024-53677. Starting with version 6.4.0, Apache deprecated the File Upload Interceptor and introduced the Action File Upload Interceptor, offering enhanced security, improved configuration, and better integration capabilities. Users are strongly encouraged to migrate to this new mechanism to mitigate potential risks.

Root Cause Analysis

The vulnerability allows an attacker to override internal file upload variables in applications that use the File Upload Interceptor.

What is File Upload Interceptor?

The File Upload Interceptor in Struts 2 is a framework component that facilitates file uploads in web applications. It is a part of the Struts 2 interceptor stack and is typically used to handle multipart form submissions where file uploads are involved. This interceptor processes file upload requests, saving uploaded files to a specified location and making them accessible to the application for further processing.

Note: The File Upload Interceptor has been deprecated since Struts 2 version 6.4.0 due to security concerns and has been replaced with the File Upload Interceptor for enhanced security and provide better integration to modern application needs.

As per Struts version 6.4.0 release File Upload Interceptor has been marked deprecated and should no longer be used.

However, it has not been removed or patched from 6.4.0 release. It still exists and appers to operate in a vulnerable way on all 6.x versions of Struts.

This issue is quite similar to CVE-2023-50164 where the file upload logic is flawed, and allows an attacker to enable paths with traversals.

An attacker can manipulate file upload params to enable paths traversal and under some circumstances this can lead to uploading a malicious file which can be used to perform Remote Code Execution.

Environment Setup

To demonstrate the vulnerability, first let’s setup our vulnerable environment and understand the issue in detail.

Setting up the target is really easy, you can build your own war app by immitating the FileUploadInterceptor Integration and defining the logic for an upload functionality

Let’s understand the file structure and the files needed,

The above screenshot shows the file structure of the vulnerable app, where the struts.xml is the Struts definition file, UploadAction.java being the primary upload logic where we’ll use a typical File Upload Interceptor-based file upload pattern, web.xml is the Web Application Deployment Descriptor (WADD) for the application and index.jsp , upload.jsp being the front end files.

<?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">
            <!-- File Upload Interceptor configuration -->
            <interceptor-ref name="fileUpload">
                <param name="allowedTypes">text/plain,image/jpeg,image/png,application/octet-stream</param>
                <param name="maximumSize">5242880</param> <!-- Max file size is 5MB -->
            </interceptor-ref>
            <interceptor-ref name="defaultStack" />
            <result name="success">upload.jsp</result>
            <result name="error">upload.jsp</result>
        </action>
    </package>
</struts>

The above XML is part of the struts.xml file, where the File Upload Interceptor is specified at <interceptor-ref name="fileUpload">

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 = "/webapps/ROOT/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;
   }
}

The primary upload logic is in UploadAction.java, where we have used a typical File Upload Interceptor-based upload pattern. We specify an uploads directory (/webapps/ROOT/uploads/), generate a new file path by appending the uploadFileName (assumed to be safe) to the directory path, and then transfer the temporary file from the Tomcat temp directory to this new location. No sanitization is performed, as we rely on Struts to handle it for us.

The remaining files can be customized as per your needs, however we are focused on only these two important files.

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

You can find the docker setup and instructions to build and run this at - https://github.com/0xdeviner/CVE-2024-53677

Exploit

We are all set to exploit this vulnerability as we are ready with our target and have understood the cause.

Let’s try and see if the upload works and we will possibly try to exploit the issue.

We will start with uploading a simple text file trying to obtain a path traversal,

The file is uploaded, now if we check the logs, we see Dst File name: test.txt which means, our attempt to simple path traversal is stripped and the file is written properly to the upload directory.

# Logs
Src File name: /usr/local/tomcat/work/Catalina/localhost/ROOT/upload_9ef5f866_c251_49fb_b593_c63fc63f3b49_00000009.tmp
Dst File name: test.txt

# Upload Directory contents
root@c6abf85581ec:/webapps/ROOT/uploads# ls
test.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. Our input aligns with the file upload parameter (referred to as upload) and targets the topmost value in the OGNL value stack. However, we intentionally avoid capitalizing "upload" in "uploadFileName" to disrupt the upload data handling logic. Due to this inconsistency, this attempt is also expected to fail.

# Logs
Src File name: /usr/local/tomcat/work/Catalina/localhost/ROOT/upload_9ef5f866_c251_49fb_b593_c63fc63f3b49_00000010.tmp
Dst File name: test1.txt

# Upload Directory contents
root@c6abf85581ec:/webapps/ROOT/uploads# ls
test1.txt  test.txt

This attempt failed, the traversal is stripped again.

Now, we will send a Uppercase payload to confuse the parameter binding process and gain control of the top OGNL stack value, as described in the Y4tacker analysis.

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

# Logs
Src File name: /usr/local/tomcat/work/Catalina/localhost/ROOT/upload_9ef5f866_c251_49fb_b593_c63fc63f3b49_00000012.tmp
Dst File name: ../test2.txt

# Upload Directory contents
root@c6abf85581ec:/webapps/ROOT/uploads# ls ../
test2.txt  uploads

This can be further escalated to RCE further, I am working on the PoC for the time being.

Mitigation

Updating Struts 2 to >=6.4.0 is not enough to prevent this exploitation as there is no patch issued for the vulnerability (as of December 18, 2024). Apache recommends to use Action File Upload Interceptor instead of File Upload Interceptor.

References


Thanks for reading, do like and follow for more such content :)

1
Subscribe to my newsletter

Read articles from Hitesh Patra directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hitesh Patra
Hitesh Patra

Security Research @ Fortinet Inc.