转载

【Struts2-代码执行漏洞分析系列】S2-057

https://cwiki.apache.org/confluence/display/WW/S2-057

问题:

It is possible to perform a RCE attack when namespace value isn’t set for a result defined in underlying xml configurations and in same time, its upper action(s) configurations have no or wildcard namespace. Same possibility when using url tag which doesn’t have value and action set and in same time, its upper action(s) configurations have no or wildcard namespace.

【Struts2-代码执行漏洞分析系列】S2-057

漏洞发现者的博客: https://lgtm.com/blog/apache_struts_CVE-2018-11776

环境搭建

下载 https://archive.apache.org/dist/struts/2.5.16/struts-2.5.16-all.zip

IDEA中打开,修改apps/showcase/src/main/resources/struts-actionchaining.xml 为:

<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
	"http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
	<packagename="actionchaining"extends="struts-default">
		<actionname="actionChain1"class="org.apache.struts2.showcase.actionchaining.ActionChain1">
			<resulttype="redirectAction">
				<paramname="actionName">register2</param>
			</result>
		</action>
	</package>
</struts>

同时查看 org/apache/struts2/default.properties:201 ,其值为true

### Whether to always select the namespace to be everything before the last slash or not
struts.mapper.alwaysSelectFullNamespace=true

访问: http://localhost:8081/${(111+111)}/actionChain1.action

【Struts2-代码执行漏洞分析系列】S2-057

url变为: http://localhost:8081/222/register2.action

111+111=222 即产生了OGNL注入。

漏洞分析

这次的漏洞可以有多种攻击向量,根据 漏洞作者blog 有:

  1. Redirect action
  2. Action chaining
  3. Postback result

以上提及的三种都属于Struts2的跳转方式。在 struts-default.xml:190(截取部分)

<result-types>
    <result-typename="chain"class="com.opensymphony.xwork2.ActionChainResult"/>
    <result-typename="redirectAction"class="org.apache.struts2.result.ServletActionRedirectResult"/>
    <result-typename="postback"class="org.apache.struts2.result.PostbackResult"/>
</result-types>

为清楚起见,这里解释一下strut2中对 默认result对象 的处理过程。这些 默认result type 都要经过 com/opensymphony/xwork2/DefaultActionInvocation.java:367 处理

private void executeResult()throws Exception {
    result = createResult();

    String timerKey = "executeResult: " + getResultCode();
    try {
        UtilTimerStack.push(timerKey);
        if (result != null) {
            result.execute(this);
        } 
    ...
}

首先通过 result = createResult() 获取到相应的result对象。如果result不为null则执行 result.execute(this); 。这个 execute 方法则由具体result对象实现。

有一些具体的result对象比如下面提到的Redirect action和Postback result,会产生一个跳转地址location,并传入org/apache/struts2/result/StrutsResultSupport.java:194:

/**
    * Implementation of the <tt>execute</tt> method from the <tt>Result</tt> interface. This will call
    * the abstract method {@link #doExecute(String, ActionInvocation)} after optionally evaluating the
    * location as an OGNL evaluation.
    *
    * @param invocation the execution state of the action.
    * @throws Exception if an error occurs while executing the result.
*/
public void execute(ActionInvocation invocation)throws Exception {
    lastFinalLocation = conditionalParse(location, invocation);
    doExecute(lastFinalLocation, invocation);
}

conditionalParse 定义如下,将会执行OGNL表达式。

/**
    * Parses the parameter for OGNL expressions against the valuestack
    *
    * @param param The parameter value
    * @param invocation The action invocation instance
    * @return the resulting string
*/
protected String conditionalParse(String param, ActionInvocation invocation){
    if (parse && param != null && invocation != null) {
        return TextParseUtil.translateVariables(
            param, 
            invocation.getStack(),
            new EncodingParsedValueEvaluator());
    } else {
        return param;
    }
}

所以可以看到重点是StrutsResultSupport中 conditionalParse(location, invocation) 的location变量。

接下来部分就关注三种result-type的具体实现和具体攻击点。

攻击点一: Redirect action

apps/showcase/src/main/resources/struts-actionchaining.xml 中注意 <result> 标签中 <type>redirectAction

<resulttype="redirectAction">
    <paramname="actionName">register2</param>
</result>

redirectAction 对应的处理类为 org.apache.struts2.result.ServletActionRedirectResult

在 com/opensymphony/xwork2/DefaultActionInvocation.java:368

【Struts2-代码执行漏洞分析系列】S2-057

跟入 redirectActionexecute 方法即 org/apache/struts2/result/ServletActionRedirectResult.java:160

public void execute(ActionInvocation invocation)throws Exception {
    actionName = conditionalParse(actionName, invocation);
    if (namespace == null) {
        namespace = invocation.getProxy().getNamespace();
    ...
}

【Struts2-代码执行漏洞分析系列】S2-057

由于在配置xml时没有指定naPmespace,所以这里的namespace为null,将会执行 invocation.getProxy().getNamespace();

【Struts2-代码执行漏洞分析系列】S2-057

所以执行后对于result对象的namespace即为 /${(111+111)}

同一函数中继续执行 172行

public void execute(ActionInvocation invocation)throws Exception {
    ...

    String tmpLocation = actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null));

    setLocation(tmpLocation);

    super.execute(invocation);
}

ActionMapping 生成如下, this.namespace 值赋为 /${(111+111)}

【Struts2-代码执行漏洞分析系列】S2-057

跟入 getUriFromActionMapping :

public String getUriFromActionMapping(ActionMapping mapping){
    StringBuilder uri = new StringBuilder();

    handleNamespace(mapping, uri);
    handleName(mapping, uri);
    handleDynamicMethod(mapping, uri);
    handleExtension(mapping, uri);
    handleParams(mapping, uri);

    return uri.toString();
}

handleNamespace 处理结果如下:

【Struts2-代码执行漏洞分析系列】S2-057

当函数返回, tmpLocation 值为 /${(111+111)}/register2.action ,然后通过 setLocation(tmpLocation) 使得location变量值为 /${(111+111)}/register2.action ,从而最终造成OGNL注入。

【Struts2-代码执行漏洞分析系列】S2-057

攻击点二: Action chaining

apps/showcase/src/main/resources/struts-actionchaining.xml 中注意 <result> 标签中 <type>chain

<resulttype="chain">
    <paramname="actionName">register2</param>
</result>

【Struts2-代码执行漏洞分析系列】S2-057

同样会先经过 result = createResult() ,然后调用 result.execute(this); 。这会进入到 com/opensymphony/xwork2/ActionChainResult.java:203

public void execute(ActionInvocation invocation)throws Exception {
    // if the finalNamespace wasn't explicitly defined, assume the current one
    if (this.namespace == null) {
        this.namespace = invocation.getProxy().getNamespace();
    }

    ValueStack stack = ActionContext.getContext().getValueStack();
    String finalNamespace = TextParseUtil.translateVariables(namespace, stack);
    String finalActionName = TextParseUtil.translateVariables(actionName, stack);
    ...
}

由于没有设定 namespace ,所以通过 invocation.getProxy().getNamespace() 使得 this.namespace 值为 /${(111+111)} 。然后调用了 String finalNamespace = TextParseUtil.translateVariables(namespace, stack); 对namespace进行OGNL解析。如下

【Struts2-代码执行漏洞分析系列】S2-057

攻击点三: Postback result

apps/showcase/src/main/resources/struts-actionchaining.xml 中注意 <result> 标签中 <type>postback

<resulttype="postback">
    <paramname="actionName">register2</param>
</result>

【Struts2-代码执行漏洞分析系列】S2-057 经过 result = createResult() ,跟入定位到 postback 这个result对象的处理方法,在 org/apache/struts2/result/PostbackResult.java:113

@Override
public void execute(ActionInvocation invocation)throws Exception {
    String postbackUri = makePostbackUri(invocation);
    setLocation(postbackUri);
    super.execute(invocation);
}

跟入 makePostbackUri1 ,在org/apache/struts2/result/PostbackResult.java:129

protected String makePostbackUri(ActionInvocation invocation){
    ActionContext ctx = invocation.getInvocationContext();
    HttpServletRequest request = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);
    String postbackUri;

    if (actionName != null) {
        actionName = conditionalParse(actionName, invocation);
        if (namespace == null) {
            namespace = invocation.getProxy().getNamespace();
        } else {
            namespace = conditionalParse(namespace, invocation);
        }
        ...
        postbackUri = request.getContextPath() + actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null));
    }
    ...

    return postbackUri;
}

【Struts2-代码执行漏洞分析系列】S2-057

获取到 namespace 值为 /${(111+111)} 。跟入 actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null)) ,其具体执行过程如攻击点一[Redirect action]提到的那样,设置namespace等参数,然后从 getUriFromActionMapping 中返回uri。最后组装的postbackUri为 /${(111+111)}/register2.action

【Struts2-代码执行漏洞分析系列】S2-057

回到前面的 execute 中通过 setLocation(postbackUri) 设置了location变量:

【Struts2-代码执行漏洞分析系列】S2-057

此后location变量传入,造成OGNL表达式注入

【Struts2-代码执行漏洞分析系列】S2-057

原文  https://chybeta.github.io/2018/09/10/【Struts2-代码执行漏洞分析系列】S2-057/
正文到此结束
Loading...