S2-048 漏洞调试及分析

2019-04-04 约 345 字 预计阅读 2 分钟

声明:本文 【S2-048 漏洞调试及分析】 由作者 cryin 于 2017-07-10 13:42:00 首发 先知社区 曾经 浏览数 6776 次

感谢 cryin 的辛苦付出!

漏洞公告

首先看Struts2 官方给出的公告信息可得知:

Apache Struts 2.3.x版本中启用了Struts 2 Struts 1 plugin 可能导致任意代码执行漏洞

关于Struts 1 plugin:

“The Struts 1 plugin allows you to use existing Struts 1 Actions and ActionForms in Struts 2 applications.

This plugin provides a generic Struts 2 Action class to wrap an existing Struts 1 Action,org.apache.struts2.s1.Struts1Action.”

以上内容可参考Struts 1 Plugin,简单的说就是org.apache.struts2.s1.Struts1Action 类为一个Wrapper类,可以将 Struts1时代的Action封装成为Struts2中的Action,以便让其可以继续在struts2应用中工作。

漏洞DEMO分析

网上已经有几篇分析很棒的文章可以参考,通过几篇分析文章可以了解到官方提供的demo程序Showcase中的Struts1 Integration就存在该漏洞,这里以struts-2.3.24-all.zip中的demo为例,详细看下代码:

SaveGangster.Action的实现类为Struts1Action,而在Struts1Action的 execute 方法中,会调用对应的Action 的 execute 方法,如下:

public String execute() throws Exception {
        ......
        ......
        Action action = null;

        try {
            //获取Action,这里this.className为SaveGangsterAction
            action = (Action)this.objectFactory.buildBean(this.className, (Map)null);
        } catch (Exception var12) {
            throw new StrutsException("Unable to create the legacy Struts Action", var12, actionConfig);
        }

        Struts1Factory strutsFactory = new Struts1Factory(Dispatcher.getInstance().getConfigurationManager().getConfiguration());
        ActionMapping mapping = strutsFactory.createActionMapping(actionConfig);
        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();
        //调用SaveGangsterAction的execute方法
        ActionForward forward = action.execute(mapping, this.actionForm, request, response);
        //获取request中ActionMessage
        ActionMessages messages = (ActionMessages)request.getAttribute("org.apache.struts.action.ACTION_MESSAGE");
        //检查ActionMessage是否为null,如果存在则继续
        if(messages != null) {
            Iterator i = messages.get();

            label36:
            while(true) {
                while(true) {
                    if(!i.hasNext()) {
                        break label36;
                    }

                    ActionMessage msg = (ActionMessage)i.next();
                    if(msg.getValues() != null && msg.getValues().length > 0) {
                        this.addActionMessage(this.getText(msg.getKey(), Arrays.asList(msg.getValues())));
                    } else {
                        //这里msg的values为null,key为Gangster ${1+3} added successfully,这里进入getText函数
                        this.addActionMessage(this.getText(msg.getKey()));
                    }
                }
            }
        }
        ......

通过(Action)this.objectFactory.buildBean(this.className, (Map)null);获取当前action,这里className为

<param name="className">org.apache.struts2.showcase.integration.SaveGangsterAction</param>

而该demo中SaveGangsterAction类继承了Action并重写了execute方法:

public class SaveGangsterAction extends Action {
    @Override
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        // Some code to save the gangster to the db as necessary
        GangsterForm gform = (GangsterForm) form;
        ActionMessages messages = new ActionMessages();
        messages.add("msg", new ActionMessage("Gangster " + gform.getName() + " added successfully"));
        addMessages(request, messages);

        return mapping.findForward("success");
    }
}

这里同时将gforn.getName()放到了ActionMessage结构中,并添加到request,属性名为org.apache.struts.action.ACTION_MESSAGE。动态调试可以知道这里name的值为:

${1+3}

继续往下看代码的执行流程。在调用SaveGangsterAction的execute方法后,接着检查了request中ActionMessage是否为空,不为空则对ActionMessage进行处理并回显给客户端。这里调用了getText函数。

this.addActionMessage(this.getText(msg.getKey()));

getText函数的存在是因为Struts2要走向世界,帮助用户解决前端国际化问题。它会根据不同的Locale(本例中为zh_CN)去对应的资源文件里面获取相关文字信息并展现。这样如果你要开发国际化的应用就不需要每种语言都整一个模版了。。这个在n1nty的分析中也有提到。

继续跟进,最后到了LocalizedTextUtil类的findText方法,这个方法分析过Struts2漏洞的都熟悉,如今年的S2-045。

public static String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
        ValueStack valueStack = ActionContext.getContext().getValueStack();
        return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
    }

这里aTextName、defaultMessage均为 “Gangster ${1+3} added successfully”

查看LocalizedTextUtil.findText函数的介绍

“If a message is found, it will also be interpolated. Anything within ${...} will be treated as an OGNL expression and evaluated as such.”

message中在${…}中的任何值都将被视为OGNL表达式被解析执行,从而导致RCE。如图

关于POC

这个漏洞并不具有通用性,且利用方式和之前的漏洞几无差别。在源代码审计的时候或许可以根据具体参数构造poc验证。可参考jas502n提供的测试POC

安全建议

S2-048漏洞原因是将用户可控的值添加到ActionMessage并在客户前端展示,导致其进入getText函数,最后message被当作ognl表达式执行。

所以开发者通过使用resource keys替代将原始消息直接传递给ActionMessage。 不要使用如下的方式

messages.add("msg", new ActionMessage("Gangster " + gform.getName() + " was added"));

参考

[1] http://bobao.360.cn/learning/detail/4078.html [2] https://github.com/jas502n/st2-048 [3] http://xxlegend.com/2017/07/08/S2-048%20%E5%8A%A8%E6%80%81%E5%88%86%E6%9E%90/ [4] http://blog.topsec.com.cn/ad_lab/strutss2-048%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

关键词:[‘技术文章’, ‘技术文章’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now