笔记:自建 MethodAccessor 防御未知 Struts2 RCE

  • A+
所属分类:安全文章

这是一个实验品,我没有在生产环境中用过,也不知道它是否会影响 Struts2 框架本身的功能(虽然我感觉应该是没有)。我给 Struts 官方发过邮件询问过相关的事情,他们没有理我。所以这里只是将它做为我调试 Struts2 框架的一篇笔记来记录。


目前为止所有的 Struts2 的 RCE 都是因为 OGNL 表达式。利用这些 RCE 自然也就是利用 OGNL。 OGNL 有内建的安全机制,Struts2 则在此基础上对此安全机制进行了一些扩展,形成自己的沙盒。


这里只简单记录一下,因为我并不想将 OGNL 在调用方法时所做的安全检查全列出来。


当利用 OGNL 调用某对象的方法或某个类的静态方法时,会先获取这个方法所在的类所对应的 MethodAccessor,在 MethodAccessor 中会进行相关安全检查,检查通过后才会最终调用目标方法。这是不可绕过的一步。


此接口如下:

public interface MethodAccessor
{
/**
    * Calls the static method named with the arguments given on the class given.
    * @param context     expression context in which the method should be called
    * @param targetClass the object in which the method exists
    * @param methodName  the name of the method
    * @param args        the arguments to the method
    *
    * @return            result of calling the method
    * @exception MethodFailedException if there is an error calling the method
    */
   Object callStaticMethod( Map context, Class targetClass, String methodName, Object[] args )
throws MethodFailedException;

   /**
    * Calls the method named with the arguments given.
    * @param context     expression context in which the method should be called
    * @param target      the object in which the method exists
    * @param methodName  the name of the method
    * @param args        the arguments to the method
    * @return            result of calling the method
    * @exception MethodFailedException if there is an error calling the method
    */
   Object callMethod( Map context, Object target, String methodName, Object[] args )
throws MethodFailedException;
}


此接口内有 callStaticMethod 方法与 callMethod 方法,分别对应利用 OGNL 调用静态方法与利用 OGNL 调用对象方法这两种情况。


若该类没有配置 MethodAccessor,则查找该类的父类、父类的父类(一直到 java.lang.Object)所对应的 MethodAccessor。


struts-default.xml 中默认配置了两个 MethodAccessor:

<bean type="ognl.MethodAccessor" name="java.lang.Object" class="com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor" />
<bean type="ognl.MethodAccessor" name="com.opensymphony.xwork2.util.CompoundRoot" class="com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor" />


也就是说默认情况下,在 Struts2 的环境中利用 OGNL 调用方法时,都会经过 java.lang.Object 类所对应的 XWorkMethodAccessor。

com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor


具体此 MethodAccessor 做了哪些安全检查以及最终是如何调用目标方法的这里不提了,因为跟主题不是太相关。知道这个概念后我们就可以进行基于黑名单的未知 RCE 的防御了。原理简单粗暴:给高危的 Java 类配置一个我们自建的 MethodAccessor,不允许它们被 OGNL 直接调用。我将这个 MethodAccessor 命名为 AntiRCEMethodAccessor,此 MethodAccessor 的 callMethod 与 callStaticMethod 方法只对调用做了日志记录,而不会真正去调用 OGNL 尝试去调用的目标方法。


在项目的 struts.xml 的 struts 标签下添加:

<!-- 禁止覆盖 method accessor -->
<bean type="ognl.MethodAccessor" name="ognl.OgnlRuntime" class="org.ninty.AntiRCEMethodAccessor" />
<!-- 禁止覆盖 ognl context 中的所有内容,包含_memberAccess,excludedClasses,excludedPackages 等等 -->
<bean type="ognl.MethodAccessor" name="ognl.OgnlContext" class="org.ninty.AntiRCEMethodAccessor" />

<!-- 禁止命令执行 -->
<bean type="ognl.MethodAccessor" name="java.lang.ProcessBuilder" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.lang.Runtime" class="org.ninty.AntiRCEMethodAccessor" />

<!-- 禁止文件相关操作 -->
<bean type="ognl.MethodAccessor" name="java.io.File" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.io.Reader" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.io.Writer" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.io.InputStream" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.io.OutputStream" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.io.RandomAccessFile" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.nio.file.Files" class="org.ninty.AntiRCEMethodAccessor" />


<!-- 禁止重定向stdout/stderr,以及获取系统变量等信息 -->
<bean type="ognl.MethodAccessor" name="java.lang.System" class="org.ninty.AntiRCEMethodAccessor" />

<!-- 禁止反射 -->
<bean type="ognl.MethodAccessor" name="java.lang.reflect.Field" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.lang.reflect.Method" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.beans.Statement" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.beans.Expression" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.lang.invoke.MethodHandles" class="org.ninty.AntiRCEMethodAccessor" />
<bean type="ognl.MethodAccessor" name="java.lang.invoke.MethodHandles.Lookup" class="org.ninty.AntiRCEMethodAccessor" />


org.ninty.AntiRCEMethodAccessor 类如下:

package org.ninty;

import com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor;
import ognl.MethodFailedException;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.Map;

/**
* Created by East on 12/03/2017.
*/
public class AntiRCEMethodAccessor extends XWorkMethodAccessor {

private static class SuspiciousRCEException extends Exception{
public SuspiciousRCEException(String e) {
super(e);
       }
}

public AntiRCEMethodAccessor(){
System.out.println("AntiRCEMethodAccessor init");
   }

private void logStackTrace(Map context, Object object, String string, Object[] objects){
StringWriter s = new StringWriter();
       String target = object instanceof Class ? ((Class) object).getName() : object.getClass().getName();
       new SuspiciousRCEException("AntiRCEMethodAccessor "+new Date().toString()+": Suspicious method call rejected: target: "+target+" method: "+string+" args:"+this.join(objects)).printStackTrace(new PrintWriter(s));
       System.out.println(s.toString());
   }

private String join(Object[] objects){
StringBuilder s = new StringBuilder();
       for (Object obj:objects
) {
s.append(obj.toString()+"n");
       }

return s.toString();
   }

@Override
   public Object callMethod(Map context, Object object, String string, Object[] objects) throws MethodFailedException {
this.logStackTrace(context, object, string, objects);
       return null;
   }

@Override
   public Object callStaticMethod(Map context, Class aClass, String string, Object[] objects) throws MethodFailedException {

this.logStackTrace(context, aClass, string, objects);
       return null;
   }
}


打完收工!(黑名单如果不全的话欢迎补充)

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: