阅读本文需要具备的知识:
影响漏洞版本:
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应该就是修复这个问题吧。