struts官方照例更新补丁,这次更新了061,从官方通告上来看与059相似,059的通告类似029与036,既然这样,那大胆猜测一下,059如果修复了,061大概率会是补丁绕过,这里贴一个官方通告地址,方便大家自行查阅:
https://cwiki.apache.org/confluence/display/WW/Security+Bulletins
进一步跟踪struts版本改动,得到以下可能存在的漏洞点以及类似黑名单:
根据官方描述,上述为黑名单机制,匹配其中的包名,当然还有类名,由于太长了,这里就不贴了。OK,既然看到类似的漏洞点了,不分析一下怎么可以呢,到此打开idea,maven导入struts2.5.26版本,分析过029的对此类应该不陌生,属于二次ognl表达式执行,看一下官方对此方法的修复情况如何
public static boolean containsExpression(String expr) { return expr != null && expr.contains("%{") && expr.contains("}"); }
对应上图可以看到如果expr中存在%{
和}
时会返回true,就不会对expr进行嵌套。更一步确定了,这次修复肯定还与二次ognl表达式有关。由此可知,这次061为059的补丁绕过。接下来开始惊心动魄的构造poc阶段
首先搭建环境,这里为了给广大白帽子一个福利,啥下载官方all包了,啥maven导版本然后再创建xml了,通通不用,直接使用idea官方maven create from archetype,如下图所示,直接一键操作搭建
创建完成后,需要对index.jsp进行一点点改造,写入特定标签
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <%@taglib prefix="s" uri="/struts-tags" %> <% String payload = request.getParameter("payload"); request.setAttribute("payload",payload); %> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Index</title> <s:head /> </head> <body> <s:a id="%{#request.payload}">testurl</s:a> <s:form action="helloWorld"> <s:textfield label="What is your name?" name="name" /> <s:textfield label="What is the date?" name="dateNow" /> <s:submit /> </s:form> </body> </html>
这时如果不了解struts框架的人就会踩到第一个坑了,包括我(菜鸡哭泣),此时传入payload字符会显示无法处理,这是因为此处关联了index.action,需要在index.action中增加一些方法,这里直接贴出
import com.opensymphony.xwork2.ActionSupport; import java.util.Date; import com.opensymphony.xwork2.conversion.annotations.Conversion; import com.opensymphony.xwork2.conversion.annotations.TypeConversion; @Conversion() public class IndexAction extends ActionSupport { private String payload; private Date now = new Date(System.currentTimeMillis()); @TypeConversion(converter = "Struts2.DateConverter") public Date getDateNow() { return now; } public String getPayload(String payload){ return payload; } public void setPayload(String payload){ this.payload=payload; } public String execute() throws Exception { now = new Date(System.currentTimeMillis()); return SUCCESS; } }
由此环境搭建完毕,可以开始愉快的调试之旅啦。这里需要对之前的漏洞有一定了解,推荐天融信的文章,分析的很好,链接放到最后。第一步,获取struts.valueStack,通过request全局变量。这一步,没有问题,第二步,开始正儿八经绕过获取context对象的限制,采用application根变量,获取org.apache.tomcat.InstanceManager到DefaultInstanceManager类,虽然推荐文章分析的很好了,但是这边啰嗦一下,贴出对应需要调用的方法
public Object newInstance(String className) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException { Class<?> clazz = this.loadClassMaybePrivileged(className, this.classLoader); return this.newInstance(clazz.newInstance(), clazz); }
当然光写,不贴图,也感受不到调试的乐趣,于是这里贴图,如何获取需要的类和调用它的方法
看到上述回显,高高兴兴开始下一阶段,进一步通过org.apache.commons.collections.BeanMap的setBean和get方法获取context,这时第二个坑遇到了,这个坑会让你甚至以为之前做的全都有错,同样用贴图,表述问题
既然内存溢出了,这简直是懵逼,没办法源代码调试吧,通过跟踪setBean方法,确认数据正确压入Map中,问题出在get方法中,观察调用链,太长具体就不贴了,可以观察到在获取context时首先会是root对象,root对象同时又是根对象,这样就会导致,root对象获取root对象,产生死循环,导致内存溢出。所以我们需要将目前获取到的context再次压入Map中,获取其他属性。所以,接着构造,这时已经拿到了context对象,ok,接下来就是操作context对象去覆盖strust的黑名单。首先获取到黑名单类
看到响应体,离成功已经很接近啦,接下来只需将安全沙箱置空,直接调用一波struts黑名单中的类,完成代码执行,接下来的就不操作了,太多公众号有写了。
参考文章:
Struts2 S2-061漏洞分析(CVE-2020-17530): https://mp.weixin.qq.com/s/RD2HTMn-jFxDIs4-X95u6g