首先看Struts2 官方给出的公告信息可得知:
Apache Struts 2.3.x版本中启用了Struts 2 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程序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验证。可参考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/