阅读本文需要具备的知识:
影响漏洞版本:
Struts 2.0.0 - Struts 2.0.11
漏洞靶机代码: (下方通过该代码进行分析, 务必下载本地对比运行)
https://github.com/dean2021/java_security_book/tree/master/Struts2/s2_002
测试POC:
http://localhost:8080/index.action?"><script>alert(1)</script><"
请求响应内容:
<body> <a href="//hello/hello_struts2.action?"><script>alert(1)</script><"=&%22%3E%3Cscript%3Ealert(1)%3C/script%3E%3C%22=">ä½ å¥½Struts2</a> </body>
通过官网安全公告 参考[1],我们大概知道问题是出在
<!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" %> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <body> <a href="<s:url action="/hello/hello_struts2" includeParams="all" ></s:url>">你好Struts2</a> </body> </html>
两个标签我们就分析
public abstract class ComponentTagSupport extends StrutsBodyTagSupport { public int doStartTag() throws JspException { // 实现子类是URL.class this.component = this.getBean(this.getStack(), (HttpServletRequest)this.pageContext.getRequest(), (HttpServletResponse)this.pageContext.getResponse()); Container container = Dispatcher.getInstance().getContainer(); container.inject(this.component); this.populateParams(); // 跟进URL类的start方法实现 boolean evalBody = this.component.start(this.pageContext.getOut()); if (evalBody) { return this.component.usesBody() ? 2 : 1; } else { return 0; } }
跟进URL类的start方法实现:
public class URL extends Component { public boolean start(Writer writer) { boolean result = super.start(writer); if (this.value != null) { this.value = this.findString(this.value); } try { // 我们在<s:url>这个标签内配置的includeParams="all" // 关于这个属性介绍,参考2 String includeParams = this.urlIncludeParams != null ? this.urlIncludeParams.toLowerCase() : "get"; if (this.includeParams != null) { includeParams = this.findString(this.includeParams); } if ("none".equalsIgnoreCase(includeParams)) { this.mergeRequestParameters(this.value, this.parameters, Collections.EMPTY_MAP); } else if ("all".equalsIgnoreCase(includeParams)) { // 我们跟进此方法的实现 this.mergeRequestParameters(this.value, this.parameters, this.req.getParameterMap()); this.includeGetParameters(); this.includeExtraParameters(); } else if (!"get".equalsIgnoreCase(includeParams) && (includeParams != null || this.value != null || this.action != null)) { if (includeParams != null) { LOG.warn("Unknown value for includeParams parameter to URL tag: " + includeParams); } } else { this.includeGetParameters(); this.includeExtraParameters(); } } catch (Exception var4) { LOG.warn("Unable to put request parameters (" + this.req.getQueryString() + ") into parameter map.", var4); } return result; }
this.mergeRequestParameters(this.value, this.parameters, this.req.getParameterMap()); 跟进实现:
protected void mergeRequestParameters(String value, Map parameters, Map contextParameters) { Map mergedParams = new LinkedHashMap(contextParameters); if (value != null && value.trim().length() > 0 && value.indexOf("?") > 0) { new LinkedHashMap(); String queryString = value.substring(value.indexOf("?") + 1); mergedParams = UrlHelper.parseQueryString(queryString); Iterator iterator = contextParameters.entrySet().iterator(); while(iterator.hasNext()) { Entry entry = (Entry)iterator.next(); Object key = entry.getKey(); if (!((Map)mergedParams).containsKey(key)) { ((Map)mergedParams).put(key, entry.getValue()); } } } Iterator iterator = ((Map)mergedParams).entrySet().iterator(); while(iterator.hasNext()) { Entry entry = (Entry)iterator.next(); Object key = entry.getKey(); if (!parameters.containsKey(key)) { parameters.put(key, entry.getValue()); } } }
从方法明明上我们已经能够看得出该方法是合并参数,通过阅读代码该方法的第三个参数也就是HttpServletRequest对象getParameterMap(), HttpServletRequest是Servlet原生对象,那这个方法具体是用来做什么的呢?下方是官方解释:
Returns a java.util.Map of the parameters of this request.
也就是返回一个map类型的request参数。我们请求的是url是:
http://localhost:8080/index.action?"><script>alert(1)</script><"
那么解析后的map就是 :
KEY = "><script>alert(1)</script><"
VAL = “”
然后进行参数合并, 并未看到对参数进行任何过滤,最后写入到html中,导致造成xss漏洞。
TIPS: 经过测试HttpServletRequest对象getParameterMap()方法只会对参数值进行转换编码,并不会对参数名进行任何处理.
Struts2框架的<s:url>标签的includeParams属性设置为all的情况下,对url参数名未做过滤,导致xss漏洞。
根据公告,我们需要升级到Struts 2.0.11.1版本。
经过对2.0.11.1的代码阅读,在UrlHelper类buildUrl方法里,第136行增加了如下修复代码:
// link是最终的生成的url for(result = link.toString(); result.indexOf("<script>") > 0; result = result.replaceAll("<script>", "script")) { }
看到这样的修复,虽然很无语,但是站在没有web安全知识的程序员角度来看待这种修复方案,能这样写也是很正常,因为大部分程序员只知道JavaScript代码是在 <script>
标签中执行。
好了,分析结束,附上一个bypass POC:
index.action?"><script 1>alert(1)</script>"
下一篇S2-003应该就是修复这个问题吧。