Struts2 漏洞分析系列 - S2-008/Debug 模式下的安全问题

阅读量277567

|评论1

|

发布时间 : 2021-12-08 10:00:30

 

0x00 漏洞概述

S2-008涉及多个漏洞,其中有着前面所存在的漏洞比如S2-007,还有S2-003&S2-005中通过参数执行OGNL表达式的方式,这里引出了一种新的执行方式,即通过Cookie的方式传参,最后一种则是devMode下支持直接执行OGNL表达式,本文着重记录最后一个漏洞的细节。

影响版本:2.0.0 – 2.3.17

复现版本:2.2.3

官方issue地址:https://cwiki.apache.org/confluence/display/WW/S2-008

 

0x01 环境搭建

由于此漏洞涉及到Struts2的执行流程,因此需要编写一个Action,内容无所谓,只是方便整个对应的路由出来,这样才能走到Struts2的执行流程里。

import com.opensymphony.xwork2.ActionSupport;

public class TestAction extends ActionSupport {
    public String execute() throws Exception {
        return "success";
    }

    public static void main(String[] args) {

    }
}

接着编写一个struts.xml用于配置路由:

<?xml version="1.0" encoding="UTF-8"?>

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

<struts>
    <constant name="struts.devMode" value="true" />
    <package name="st2-demo" extends="struts-default">
        <action name="test" class="TestAction">
            <result name="success">index.jsp</result>
        </action>
    </package>
</struts>

 

0x02 漏洞分析

处理DebugMode的拦截器为DebuggingInterceptor,我们在其intercept方法下个断点,随后访问index.action触发拦截,相关代码:

org.apache.struts2.interceptor.debugging.DebuggingInterceptor#intercept
public String intercept(ActionInvocation inv) throws Exception {
        boolean cont = true;
        if (this.devMode) {
            ActionContext ctx = ActionContext.getContext();
            String type = this.getParameter("debug");
            ctx.getParameters().remove("debug");
            if ("xml".equals(type)) {
                inv.addPreResultListener(new PreResultListener() {
                    public void beforeResult(ActionInvocation inv, String result) {
                        DebuggingInterceptor.this.printContext();
                    }
                });
            } else if ("console".equals(type)) {
                this.consoleEnabled = true;
                inv.addPreResultListener(new PreResultListener() {
                    public void beforeResult(ActionInvocation inv, String actionResult) {
                        String xml = "";
                        if (DebuggingInterceptor.this.enableXmlWithConsole) {
                            StringWriter writer = new StringWriter();
                            DebuggingInterceptor.this.printContext(new PrettyPrintWriter(writer));
                            xml = writer.toString();
                            xml = xml.replaceAll("&", "&amp;");
                            xml = xml.replaceAll(">", "&gt;");
                            xml = xml.replaceAll("<", "&lt;");
                        }

                        ActionContext.getContext().put("debugXML", xml);
                        FreemarkerResult result = new FreemarkerResult();
                        result.setFreemarkerManager(DebuggingInterceptor.this.freemarkerManager);
                        result.setContentType("text/html");
                        result.setLocation("/org/apache/struts2/interceptor/debugging/console.ftl");
                        result.setParse(false);

                        try {
                            result.execute(inv);
                        } catch (Exception var6) {
                            DebuggingInterceptor.log.error("Unable to create debugging console", var6);
                        }

                    }
                });
            } else if ("command".equals(type)) {
                ValueStack stack = (ValueStack)ctx.getSession().get("org.apache.struts2.interceptor.debugging.VALUE_STACK");
                String cmd = this.getParameter("expression");
                ServletActionContext.getRequest().setAttribute("decorator", "none");
                HttpServletResponse res = ServletActionContext.getResponse();
                res.setContentType("text/plain");

                try {
                    PrintWriter writer = ServletActionContext.getResponse().getWriter();
                    writer.print(stack.findValue(cmd));
                    writer.close();
                } catch (IOException var14) {
                    var14.printStackTrace();
                }

                cont = false;
            }
        }

        if (cont) {
            boolean var13 = false;

            String var16;
            try {
                var13 = true;
                var16 = inv.invoke();
                var13 = false;
            } finally {
                if (var13) {
                    if (this.devMode && this.consoleEnabled) {
                        ActionContext ctx = ActionContext.getContext();
                        ctx.getSession().put("org.apache.struts2.interceptor.debugging.VALUE_STACK", ctx.get("com.opensymphony.xwork2.util.ValueStack.ValueStack"));
                    }

                }
            }

            if (this.devMode && this.consoleEnabled) {
                ActionContext ctx = ActionContext.getContext();
                ctx.getSession().put("org.apache.struts2.interceptor.debugging.VALUE_STACK", ctx.get("com.opensymphony.xwork2.util.ValueStack.ValueStack"));
            }

            return var16;
        } else {
            return null;
        }
    }

从代码中可以看到,这里首先通过getParameter方法获取请求中debug参数对应的值,这里会根据debug的值进行不同的调用,分别为:

  • xml
  • console
  • command

其中当type为command时,会从请求中继续读取expression参数对应的值,并通过stack.findValue执行它,最后写入返回页面中,因此可以利用之前S2-003&S2-005部分的Payload完成后续的RCE。

比较奇怪的是,在2.0.5版本的Struts2中,我发现这里是无法获取到stack的,因此stack为null,此时后续的调用会触发空指针异常,自然也就没办法执行OGNL表达式了,所以大家如果复现的话最好还是用稳定复现的版本进行复现。

利用效果:

 

0x03 修复方案

由于debug模式本身就不该开放在生产模式,因此由debug模式引发的漏洞并没有对应的修复方案(DIFF了一下Struts相关Jar得出的结论),最新的DIFF结果如下:

从上述DIFF的结果可以看出,实际上并没有修复此漏洞,在开Debug模式下同样会造成OGNL表达式的解析,如果要修,只能从根源上修复,那就是不在生产环节中开debug模式:P

本文由tlmn原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/261742

安全客 - 有思想的安全新媒体

分享到:微信
+12赞
收藏
tlmn
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66