BlackHat2016——JDNI注入/LDAP Entry污染攻擊技術研究

(一)基本概念

1.1 JNDI

JNDI(Java Naming and DirectoryInterface),直譯爲命名與目錄接口。JNDI是一組客戶端通過名稱(Naming)來尋找和發現數據和對象的API。

JNDI的概念分爲命名系統和目錄系統:

(1)    命名系統(Naming Service):將實體使用名稱和值的方式聯繫起來,俗稱綁定。

l  DNS:將機器的網絡地址和域名進行映射;

l  文件系統:將文件名和存儲在磁盤的數據進行映射。

(2)    目錄系統(Directory Service):是一種特殊的命名系統,目錄系統中支持“目錄對象”的存儲和查詢。LDAP就是一種目錄系統,允許以樹狀的形式存儲目錄對象,並且可以對這些對象進行索引。

明確一下對象的概念,對象可以在本地,也可以部署在遠程服務器。學習過RMI原理的同學應該對遠程對象並不陌生,其實RMI就是JNDI的一種,類似的還有CORBA,LDAP以及衆所周知的DNS服務。

 

1.2 JNDI的代碼片段

上圖的代碼片段是使用JNDI接口來創建RMI服務,這和sun.rmi.*包提供的創建方式有所不同,關鍵在於map對象env和上下文對象ctx,通過這兩個對象來標識一些信息。這裏有幾個方法要說明一下:

(1)    bind方法:將服務名稱和實體進行綁定,比如這裏調用bind方法來使用foo字符串指定一個字符串”Sample String”。當然這個代碼直接運行會出錯,原因在於bind方法接收的對象必須是遠程對象。源碼如下:

(2)lookup方法:從系統中尋找命名標識的對象。這裏使用foo字符串來在命名與目錄系統中尋找對應的對象(字符串對象)。

最後print出的是“Sample String”

 

1.3 引用與地址

在JNDI系統中,需要存儲一些對象,存儲對象的方式通常會採用存儲該對象的引用的方式。對於學習過OOP概念的同學,對象的引用並不難理解。所謂引用(Reference)就是指在內存中定位對象的一個指針。通過對象的引用,我們可以在JNDI系統中操作對象或者獲取對象的一些信息。

比較有趣的是,使用Reference對象可以指定工廠來創建一個java對象,用戶可以指定遠程的對象工廠地址,當遠程對象地址用戶可控時,這也會帶來不小的問題。

 

1.4 遠程代碼與安全管理器

1.4.1 Java中的安全管理器

Java中的對象分爲本地對象和遠程對象,本地對象是默認爲可信任的,但是遠程對象是不受信任的。比如,當我們的系統從遠程服務器加載一個對象,爲了安全起見,JVM就要限制該對象的能力,比如禁止該對象訪問我們本地的文件系統等,這些在現有的JVM中是依賴安全管理器(SecurityManager)來實現的。

 

JVM中採用的最新模型見上圖,引入了“域”的概念,在不同的域中執行不同的權限。JVM會把所有代碼加載到不同的系統域和應用域,系統域專門負責與關鍵資源進行交互,而應用域則通過系統域的部分代理來對各種需要的資源進行訪問,存在於不同域的class文件就具有了當前域的全部權限。

關於安全管理機制,可以詳細閱讀:

http://www.ibm.com/developerworks/cn/java/j-lo-javasecurity/

 

1.4.2 JNDI安全管理器架構

對於加載遠程對象,JDNI有兩種不同的安全控制方式,對於Naming Manager來說,相對的安全管理器的規則比較寬泛,但是對JNDI SPI層會按照下面表格中的規則進行控制:

 

針對以上特性,黑客可能會找到一些特殊場景,利用兩者的差異來執行惡意代碼。

 

 

(二)click-to-play繞過

2.1 點擊運行保護

有了以上的基礎知識作爲鋪墊,我們來了解下“Click-to-play”的繞過(CVE-2015-4902),該0day是從趨勢科技捕獲的一個蠕蟲病毒Pawn Storm中發現的。詳細請參考趨勢科技的具體blog:

http://blog.trendmicro.com/trendlabs-security-intelligence/new-headaches-how-the-pawn-storm-zero-day-evaded-javas-click-to-play-protection/

要想真正理解這個CVE的原理,還需要一些基礎知識的講解。

 

2.2 JNLP協議

JNLP全稱爲Java Network Launch Protocol,這項技術被用來通過URL打開一個遠程的Java可執行文件。通過這個技術,可以快速部署applet或者web應用。而在攻擊場景下,攻擊者用於部署applet應用。

 

2.3 jndi.properties文件

jndi.properties文件用來創建Context上下文對象。如果正常使用代碼的方式,我們可以創建一個Properties對象來設置一些JNDI服務需要的一些配置,然後通過這個對象創建出相關的上下文對象:

Properties p = new Properties(); 
p.put(Cotnext.PROVIDER_URL, "localhost:1099 ");//主機名和端口號 
//InitialContext的創建工廠類
p.put(Context.InitialContextFactroy, "com.sun.InitialContextFactory"); 
InitialContext ctx = new InitialContext(p); 

如果使用jndi.properties文件來創建上下文對象,我們可以將這些配置寫入到properties文件中,從而使代碼的可配置性更高:

java.naming.factory.initial=com.sun.NamingContextFactory 
java.naming.provider.url=localhost:1099 
如果直接創建初始上下文,如下: 
InitialContext   ctx   =   new  InitialContext(); 
InitialContext的構造器會在類路徑中找jndi.properties文件,如果找到,通過裏面的屬性,創建初始上下文。 

兩種方式是相同的效果。

 

2.4 攻擊思路

攻擊發生前,攻擊者做了這樣三件事情:

(1)    配置一個惡意的web頁面,該頁面包含一個applet應用,具體代碼如下:

(1)    攻擊者創建一個RMI服務(公網IP)

(2)    攻擊者創建一個託管java惡意代碼的服務器(公網IP)

 

接下來就是攻擊發生的具體步驟了:

Ø  在受害者機器上,訪問含有applet應用的html頁面之後,瀏覽器進程會啓動jp2launcher.exe,然後從惡意服務器請求init.jnlp文件。

Ø  惡意服務器上返回一個jnpl文件,文件內容如下:

這裏jnpl文件中,progress-class指定爲javax.naming.InitialContext。通過官網文檔,我們獲知這個屬性指定的類名需要實現DownloadServiceListener接口才行,但是jre似乎沒有校驗這個情況。

Ø  受害者主機會執行這個類的構造方法,InitialContext構造方法會從惡意服

務器上請求jndi.properties文件,用於創建context對象,該文件如下:

可以看到指定了Context的工廠類爲RMI服務的,並且指定rmi的URL。

Ø  然後受害者主機與RMI服務器建立了通訊,隨後客戶端發起查找Go對象

(其實就是個惡意的工廠類)的請求,相應地,RMI服務器返回一個惡意的Go.class。

Ø  這個惡意文件在受害者主機被執行,從而實現靜默執行效果。

從上面的過程來看,攻擊者用到了JNDI實現了靜默執行applet的方法,從而執行了惡意代碼,繞過click-to-play,過程非常巧妙和精彩。

 

(三)JNDI注入漏洞

3.1 攻擊條件

漏洞利用的條件:

(1)    上下文對象必須通過InitialContext或者它的子類(InitialDirContextor InitialLdapContext)來實例化。

(2)    InitialContext的一些屬性可以通過傳入lookup方法的參數進行修改。

關於條件中的第二條,來解讀一下,首先看下面的代碼片段:

如果攻擊者對傳入lookup的參數是可控的,那麼無論context中配置過什麼URL,比如這裏指定了RMI的URL爲rmi://sercure-server:1099,但是攻擊者如果在lookup中指定一個絕對路徑,如rmi://evil-server:1099/foo,那麼會以lookup參數指定的URL爲準。

 

 

3.2 RMI攻擊向量

3.2.1 RMI介紹

RMI全稱爲遠程方法調用,上圖描述了RMI的架構,可以看到,客戶端和服務器的通訊運用了代理對象,分別是Stub和Skeleton對象,這兩個代理對象負責實現客戶端和服務器之間的通訊,提供遠程對象的副本,返回遠程對象調用的結果等功能,從而實現遠程對象的調用。

 

3.2.2 JNDI Ref payload

Reference是JNDI中的對象引用,因爲在JNDI中,對象傳遞要麼是序列化方式存儲(對象的拷貝,對應按值傳遞),要麼是按照引用(對象的引用,對應按引用傳遞)來存儲,當序列化不好用的時候,我們可以使用Reference將對象存儲在JNDI系統中。

JNDI提供了一個Reference類來表示某個對象的引用,這個類中包含被引用對象的類信息和地址。地址屬性是用RefAddr類表示。用Reference也可以創建對象:

上面的代碼片段是官方文檔中的demo,通過Reference構造函數,傳入要實例化類的全名、地址信息、工廠類的全名、工廠類的地址等信息,就能實例化一個類,值得注意的是,這裏支持傳入工廠類的URL地址,也就是支持遠程工廠類的引入。

如果在RMI服務器端使用Reference創建遠程對象後,綁定到rmi中:

注意,這裏的FactoryURL需要攻擊者可控,因此攻擊者可以自己寫一個惡意的工廠類,然後在服務端執行惡意代碼。

看兩個Demo來說明一下:

場景1:攻擊者可控FactoryUrl

首先是提供正常RMI服務的服務器,代碼看上去是這樣的:

如果上面圈出來的地方是攻擊者可控的話,那麼攻擊者通過這個URL可以構造一個工廠類來影響服務器端的邏輯。

這個工程類如果包含了惡意的代碼,比如可以把工廠類構造成這樣:

這裏demo中,惡意代碼放在了getObjectInstance裏,因爲在執行lookup時,JNDI會調用工廠對象中的getObjectInstance方法:

其實直接把惡意代碼放在工廠類的構造函數中也行,因爲lookup執行時會對其進行實例化,相關代碼可以從RMI實現中找到。

當然我們還需要自己做一個RMI服務器,當然是惡意的服務器,代碼如下:

在惡意服務器上,我們將惡意的工廠類綁定在RMI服務上。

這樣,服務器上bind的對象,實際上是我們工廠方法所提供的。一旦有客戶端連接這個受到污染的RMI服務器,並且調用了lookup方法來尋找受污染的遠程對象,惡意代碼就會被執行。

寫個客戶端代碼模擬一下:

3.2.3 直接注入lookup

直接看代碼:

清晰明瞭,沒啥可說的。而且原理實際上和ref注入差不多,爲了好懂,還是舉個栗子。

 

場景2:利用惡意工廠類exploit

上個場景有點蜜汁難懂,其實是我自己YY的,我們來看下議題的作者給出的姿勢。

首先,我們在可控的服務器上搭建一個RMI服務,這個服務上綁定一個惡意的工廠類:

惡意服務器開放了12345端口,並綁定了一個惡意的工廠類,這次我們將惡意代碼放入到這個工廠類的構造函數中,比如:

然後,如果某個應用的lookup方法的參數是我們可控的,就可以填入我們的惡意服務URL,類似下面這種。

原理很簡單,工廠類最終會在客戶端進行實例化,實例化時就會調用構造函數中的代碼,從而達到任意代碼執行的效果,注意,實例化工廠類的是NamingManager,根據JNDI的架構,這個類是不受Java安全管理器約束的。

 

 

3.2.4 惡意遠程對象

通過遠程對象來進行JNDI注入,難度比較大,要求有權限修改codebase以及java.rmi.server.useCodebaseOnly必須爲False(JDK 7u21後默認爲true)。

由於運用難度大並且有很大的侷限性,所以這裏就不進行介紹了。

 

3.2.5 攻擊過程

配合Ref Payload的Demo,我們不難理解通過RMI進行JNDI注入的攻擊流程:

(1)    首先攻擊者將RMI絕對路徑注入到lookup方法中。

(2)    受害者RMI服務器會請求攻擊者事先搭建好的惡意RMI服務器

(3)    惡意服務器返回Payload(惡意遠程對象)

(4)    惡意代碼在受害者服務器執行。

值得注意的是,像InitialContext.rename()和InitialContext.lookupLink()方法也會受到影響,因爲它們最終還是調用了lookup方法。

 

 

 

3.2.6 Toplink/EclipseLink

JPA(持久化技術)是ORM的統一標準,Toplink是JPA的一種實現,常用的hibernate也是。EclipseLink是以Toplink爲基礎的開源項目。來看看JNDI的真實場景:

在基礎操作中處理POST請求的過程中,調用了callSessionBeanInternal,跟進這個方法:

Lookup傳入的參數是可控的,通過http請求可以做到,標準的JNDI注入。攻擊者可以利用JNDI注入漏洞來實現任意代碼執行。

 

3.2.7 與反序列化配合

本質上原理相同,readObject方法中有可控的lookup參數。

比如Spring框架爆出的這個反序列化漏洞,執行過程如下:

org.springframework.transaction.jta.JtaTransactionManager.readObject()方法中調用了IntinailContext.lookup方法,調用過程如下:

l  initUserTransactionAndTransactionManager()

l  initUserTransactionAndTransactionManager()

l  JndiTemplate.lookup()

l  InitialContext.lookup()

InitialContext.lookup()這個方法中的傳入參數”userTransactionName”是用戶可控的,所以造成了JNDI注入。BlackHat上的議題中還提到了其他的例子,這裏就不一一介紹了。

 

3.3 CORBA攻擊向量

CORBA的JNDI注入原理和RMI的差不多,但是有SecurityManager的限制,然而,議題的演講者找到了一個繞過SecurityManager的方法,但是由於正在被修復中,所以在議題中並沒有透漏這個方法。感興趣的同學可以關注一下,在幾個繞過中,已經有一個獲得了CVE編號(CVE-2016-5018)。

 

 

3.4 LDAP攻擊向量

3.4.1 LDAP基礎

LDAP是輕量級目錄訪問協議,通過LDAP,用戶可以連接,查詢,更新遠程服務器上的目錄。

對象在LDAP上有兩種存儲方式:

(1)    利用Java序列化方式

(2)    利用JDNI的References對象引用

這兩種方式都有可能造成命令執行。

3.4.2 攻擊流程

1.      攻擊者提供一個LDAP的絕對路徑URL注入到JNDI的lookup方法

2.      受害者服務器連接到攻擊者的惡意LDAP服務,並返回一個惡意的遠程對象引用。

3.      受害者服務器對JNDI遠程對象引用Reference進行decode操作。

4.      受害者服務器獲取到了惡意的工廠對象。

5.      受害者服務器實例化這個工廠對象。

6.      工廠對象中的惡意代碼被觸發執行。

LDAP的情境下,對於lookup方法的注入和漏洞觸發原理本質上和RMI的一致。

 

3.4.3 LDAP實體投毒

實際上,lookup方法惡意注入的場景是非常少見的。大部分的操作都是在對象層面的操作,比如增刪改查等。

LDAP編程中,通常會使用search方法來查詢一個目錄對象,單純的一個查詢是無法做到命令執行的,但是議題作者發現,當returnObjFlag設置爲true時,攻擊者可以控制LDAP的返回並引發任意命令執行的漏洞。

對象返回查詢

LDAP編程中使用SearchControls對象作爲參數來標識查詢範圍以及查詢返回值的形式。這個對象中有個方法是setReturningObjFlag(boolean),當設置爲true時(默認爲false),使用search方法查詢後會返回一個對象結構。

當returnObjFlag設置爲true時,查看源碼,可以看到調用了decodeObject方法轉化爲對象。

 

Java對象表現協議

在RFC 2713中,詳細的定義了不同的Java對象在LDAP目錄系統中的表現和存儲形式。

1.      序列化對象

序列化對象在LDAP中的表示如下:

l  javaClassName:類的全稱

l  javaClassNames:類定義所繼承的父類,接口的名稱集合

l  javaCodebase:指向class定義的位置

l  javaSerializedData:包含序列化之後的對象數據

 

2.      Marshalled Objects

和序列化對象差不多,但是會記錄javaCodebase.

 

3.      JNDI References

引用類型對象包含了javaClassName,javaClassNames, javaCodebase。除此之外,還有:

l  javaReferenceAddress:存儲引用地址的列表。

l  javaFactory:存儲工廠類的類名全稱。

 

 

攻擊向量

1.      反序列化

當JNDI中對象的javaSerializedData不爲空時,decodeObject方法就會對這個字段的內容進行反序列化(Obj.decodeObject(Attributesattrs)):

這裏javaCodebase可以指定遠程的URL,黑客只需要在readObject方法中編寫惡意代碼就能執行,當然需要服務器端配置com.sun.jndi.object.trustURLCodebase=true

來避開JVM的安全管理器。

當LDAP服務器沒有這種設置時,攻擊者仍然可以使用一些存在於服務器端的有漏洞的類來執行代碼。

LDAP投毒的代碼如下,這裏是指定了javaCodebase進行攻擊:

2.      JNDIReference

LDAP中,也是由Naming Manager來處理引用類型的對象,並做實例化的。Naming Manager會檢查javaFactory和javaCodebase是否是存在的,如果存在,則從javaCodebase中獲取javaFactory進行實例化。正如前面所說的,對於Naming Manager,JVM的安全管理機制太過於寬鬆。因此,攻擊者就可以通過控制這些屬性來執行惡意代碼。

下面的代碼是Obj.decodeObject(Attributesattrs)中實例化Reference的代碼:

這裏的decodeReference方法中對Reference進行了組裝:

可以看到這個代碼和JNDI注入的代碼是一致的。

攻擊代碼如下:

 

3.      RemoteLocation

javaRemoteLocation屬性在RFC中是被廢除的,但是JNDI還是能支持這個屬性的處理。相關代碼如下:

可以看到,當指定javaRemoteLocation時,JNDI會根據URL獲取到對應的Reference,實例化之後就會觸發漏洞代碼,和RMI的情景如出一轍。

3.4.4 攻擊場景

在LDAP中修改對應的Java屬性,當LDAP中查詢後實例化查詢結果時,就會觸發漏洞。具體來講,是LdapSearchEnumeration類對LDAP查詢響應進行實例化,本質上還是通過注入外部的工廠類來污染Reference。

對攻擊過程進行總結,大致是兩個方向:

1.      針對LDAP條目

(1)    攻擊者污染一個LDAP條目,並且注入惡意的Java協議屬性。

(2)    攻擊者向LDAPserver發起一個查詢(比如LDAP認證的時候)。

(3)    受害應用執行LDAP查詢並獲取受到污染的實體。

(4)    受害應用將條目轉換爲java對象。

(5)    受害應用從攻擊者控制的服務器上獲取惡意的工廠類。

(6)    受害應用在實例化工廠類的時候執行了惡意代碼。

 

 

 

 

2.      針對LDAP響應

(1)    攻擊者強制受害應用發起一個LDAP查詢(比如認證的時候),或者等待該應用發起一次LDAP查詢。

(2)    應用發起一次LDAP查詢,並獲取一個條目

(3)    攻擊者攔截並修改LDAP查詢的響應,將惡意的Java協議屬性注入到響應中。

(4)    受害應用對該響應進行實例化時觸發惡意代碼

 

3.4.5 返回對象的查詢方式

設置returnObjFlag爲true的寫法還是挺常見的,因爲查詢過後直接返回對象,操作起來非常方便。

 

Spring Security案例

Spring security是一個Java應用常見的認證和鑑別的框架。

這個庫提供了一個查詢指定用戶名的方法:

FilterBasedLdapUserSearch.searchForUser(String username). 這個方法是Spring Security獲取正在認證的用戶信息的。這個方法用到了SpringSecurityLdapTemplate類:

跟進這個方法:

繼續跟進,可以看到查詢的代碼:

 

在buildControls中,可以看到設置了RETURN_OBJECT爲true:

顯然,這個漏洞是針對條目的一種形式,由於設置了查詢結果返回爲java對象,JNDI會自動將查詢結果進行某種decode來轉爲Java對象,實例化過程中觸發漏洞。通過修改java協議屬性來複現:

1.      首先寫一個惡意的工廠類,在構造函數中執行惡意代碼。

2.    污染條目

重點是修改了javaFactory和javaCodebase,指向了我們的惡意工廠類。

3.      觸發search方法

只需要嘗試登陸一下即可觸發LDAP執行search操作,從而執行我們的惡意代碼。

 

1.      執行惡意代碼。

 

 

Spring LDAP案例

還是同樣的原因,這裏只分析下源碼:

受影響的是authenticate方法,調用了search方法,跟進之後發現也同樣設置了returnObjFlag,利用思路和前面一致。

 

 

(四)總結

議題中介紹了兩種新型的攻擊方式——JNDI攻擊和LDAP條目污染。兩種方式都是非常高危的漏洞,並且可以執行任意的代碼。

爲了防範這兩種類型的漏洞,可以做以下措施:

1.      不要將不可信的數據傳入InitialContext.lookup方法中。

如果必須這麼做,那麼要確保參數不是絕對路徑的URL。

2.      使用安全管理器時,需要仔細審計安全策略。

3.      儘可能禁止遠程的codebase

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