STRUTS2的getClassLoader漏洞利用

wyzsk發表於2020-08-19
作者: 阿里巴巴安全中心 · 2014/03/14 18:56

0x00 摘要


2012年,我在《攻擊JAVA WEB》,文中提多關於“classLoader導致特定環境下的DOS漏洞”,當時並沒有更加深入的說明,這幾天struts官方修補了這個漏洞,本文是對這個漏洞的深入研究。

0x01 正文


這一切,得從我們控制了classLoader說起,曾經寫過一篇文章,提到了一個小小的技術細節,非常不起眼的一個雞肋。 引用《Spring framework(cve-2010-1622)漏洞利用指南》

Struts2其實也本該是個導致遠端程式碼執行漏洞才對,只是因為它的欄位對映問題,只對映基礎型別,預設不負責對映其他型別,所以當攻擊者直接提交URLs[0]=xxx時,直接爆欄位型別轉換錯誤,結果才僥倖逃過一劫罷了。

tomcat8.0出來後,這個問題爆發了,這是一個雞肋漏洞的逆襲。

在struts2任何一個action執行之前,一旦接受到使用者提交引數xx=zzzzz時,就由Ognl負責呼叫對應的當前action的setXxx方法,至於set方法到底是幹什麼的,其實不重要,裡面的邏輯也不重要,我們只關注這個方法呼叫了,引數傳遞了。這種對屬性的改變,有時候是可以很大程度的影響後續複雜邏輯。

普及一點基礎

Object是java的基礎類,所有的class生成的物件,都會繼承Object的所有屬性和方法,因此當前action無論是什麼程式碼,必須有Object自帶的getClass方法,這個方法會返回一個Class物件,Class物件又一定會有getClassLoader方法,最終在每個action都可以

#!java
getClass().getClassLoader()

拿到當前的ClassLoader。

我研究這個問題,在幾年前了,這個東西理解起來不容易,尤其是各個web容器不一致,剛巧當時有個阿里巴巴內部《tomcat等容器的classLoader載入原理》培訓,收穫匪淺。本文篇幅有限,簡單的講一下。

在JRE啟動中,每個Class都會有自己的ClassLoader。web容器,為了方便的管理啟動過程,通常都有實現自定義的ClassLoader。《Spring framework》的漏洞的利用場景真的非常幸運,利用了web容器的特性getURLs方法,所有容器的servlet的ClassLoader都會透過繼承父類UrlClassLoader得到getURLs這個方法,所以這個漏洞可以不受容器影響。事實上,每個容器的ClassLoader都是自己實現的,環境必然會有所不同,那次struts2僥倖逃過一劫,所以我的一個關注點,一都放在幾大web容器的ClassLoader程式碼變化上,哪天看到tomcat8居然把resources放進ClassLoader上,而ServletContext剛巧掛在resources上,頓時知道肉戲來了。

上傳webshell的可能性研究

多次的遠端程式碼執行漏洞洗禮,我一直在腦海裡模擬“ServletContext被控制了,這次能幹什麼”,究竟有哪些路線,可以通往程式碼執行的領域。

比如:Struts2會去servletContext裡取到一個值,然後把它作為Ognl執行掉。這個太簡單了,我自己都不信。

Ognl的Context樹形結構:

enter image description here

servletContext被轉換成Map,變成了圖中的application子項,這個位址很尷尬,如果是上一層Node,從上到下找到value Stack,確實有實現這個思路的可能,但現在看來,這條路斷了,它不支援找到父節點。經過多次找尋後,確認Ognl出局,只能從web容器本身入手。

執行在Tomcat8下的struts,在隨便哪個action程式碼中,插入這段,下斷點,

#!java
this.getClass().getClassLoader();

enter image description here

任何一個Action的classLoader都是org.apache.catalina.loader.WebappClassLoader,這是漏洞的源頭。

我的思路,是給context賦予初始化引數readOnly=false。因為在tomcat上,所有的請求,都會預設由defaultServlet上處理,或者由jspServet處理。只要在context初始化時,readOnly=false,接下來defaultServlet就會真的處理PUT請求DELETE請求等等大威力請求,於是我們可以直接PUT檔案上來。

這個思路的前提,是defaultservlet再被初始化一次。現實很殘酷,這個思路最終沒有得到執行,原因是servlet只會初始化一次,而readOnly屬性只有在servlet初始化時,才會真的給servet的readOnly屬性賦值。這次突破,宣告失敗。

幾個這個漏洞的除錯小技巧:

1. 僅僅從debug上檢視ognl的賦值情況,是不準確的,debug只能看到這個類定義好的變數。

如果有一個程式碼是這樣的:

#!java
public void setName(String name){…}

但是並沒有定義過這個屬性,這時debug無法看到這個東西,但是其實ognl可以直接透過name=zzzzz呼叫,並且能把zzzz傳遞過去。

2. 或者只有一個私有屬性,但是沒有set方法,其實也是ognl不能賦值的。

這個debug,觀察這個漏洞時,僅僅是個參考,要真正深入進去看程式碼才能和ognl的視線保持一致。

3. 一個final的物件,或者只是get方法返回一個物件,看起來像是隻讀的,其實物件的屬性還是可以改的,這個只是物件的引用。

你可以理解為指標指向的地址不能變,但是指向的那個物件的屬性,是可以修改的。

舉例:

#!java
public User getUser()
{
     return this.user;
}
public final User user;

這兩處程式碼,其實真正返回給OGNL的都是user物件,物件的屬性只要還有set方法,也都是可以被修改的。依然可以透過

url?user.name=xxx

對user的name賦值。

struts2執行在tomcat8.0.1rc5(2013,11月前)的任意檔案讀取漏洞

在tomcat的環境下,classLoader會掛載一個resources,類名叫做“StandardRoot”,這個恐怖的東西,和tomcat的資原始檔管理有關,debug看到的第一個屬性就是非常危險的屬性“allowLinking”。

enter image description here

這個事情,要從很久很久以前,struts修補的一個任意檔案讀取漏洞說起。

http://struts.apache.org/release/2.3.x/docs/s2-004.html

這是一個目錄列表+檔案讀取漏洞,修補方案非常陰險,沒有采用正規的手段,在框架層解決漏洞,而是利用了web容器的一個公約特性,jsp的web容器都遵守一個規則。

當一個路徑叫做“/var/www/tomcat/webapps/demo/struts/../”時,呼叫

#!java
getClassLoader().getResource(Path)

返回路徑為:

/var/www/tomcat/webapps/demo/

會把/../去掉,並且直接到達目的目錄。

這個叫做web容器特性,由web容器說了算,哪天web容器生氣了,想變了,struts沒有話語權。事實上,我一直喜歡講框架安全,其中一條準則,就是“框架不要依靠web容器的特性做防禦”,當然,今天不討論這個話題,只是稍微做個鋪墊。

當時修補程式碼為:

enter image description here

使用者提交struts/../時,pathEnding="struts/../"

但是

resourceUrl="/var/www/tomcat/webapps/demo/"

所以並不以pathEnding結尾。這種猥瑣的做法,當時確實有效。

tomcat8這個版本突然抽風了,重寫了這個方法,還真的返回了

/var/www/tomcat/webapps/demo/struts/../

宣告淪陷。但是程式碼實際執行中,有個要求,就是“StandardRoot.allowLinking”必須是true(預設肯定是false)。

機會來了。首先提交:

http://localhost:8080/demo/t1.action?class.classLoader.resources.allowLinking=true

debug可以看到已經是true。

然後按照以前的攻擊方法:

enter image description here

就可以輕易讀取任意檔案,拿到資料庫密碼等等。

這是兩個漏洞結合的成果,非常遺憾的是,在RC5這個版本之後,有人給tomcat提交了一個需求,大概在2013年11月左右,tomcat的一個不重要的需求中(剛好這個需求涉及到資原始檔相關程式碼),tomcat維護人員也許並沒有意識到了這裡存在讀取資原始檔的威脅,僅僅是把讀取資源功能重新抽象規劃了一次,結果順帶修補了這個漏洞,這問題產生的理由非常冤屈,修補的理由非常冤屈,最鬱悶的是我,活生生的,0day沒了。原本沾沾自喜以為可以大殺四方,結果大神打了個噴嚏。

最後順帶說一句,這個漏洞只在windows下正常,linux下未知原因抽風。

tomcat8下黑掉struts2應用的首頁

但是不要緊,tomcat是不可能給struts解決根本問題的,standardroot暴露出來,可以順帶影響很多東西。這個算DDOS麼?其實我可以把“Hacked by kxlzx”寫到你的應用首頁去

成因非常的“正常”,因為這個context屬性代表了tomcat的/conf/context.xml檔案的配置,我現在把path給你改了,那麼struts去處理result時,會用路徑拼接讀取檔案,現在目錄名被改掉了,自然就讀不到檔案了,可惜這裡不能00截斷,否則又是一個任意檔案讀取漏洞。 下面是被幹掉的網站,訪問任何一個action,都會有如下效果:

enter image description here

看看debug的情況:

enter image description here

當前action叫做T1,會找到T1.jsp,但是現在目錄名已經被修改了,所以報錯。

enter image description here

這個問題可以影響tomcat8所有版本下執行的struts2,對了,你們得自己設計EXP哈,不要亂入。

jetty7關機指令

既然提到了web容器,只有研究tomcat,肯定不能覆蓋大家關心的地方,於是我選擇了另一個開源免費並且使用量大的輕量級web容器:jetty。

現在先看看jetty是否有突破的口子。這次講解路線反過來,先找個影響“不大”,各位“不是”很關心的漏洞。

還是先看看web結構,使用老辦法斷點:

#!java
this.getClass().getClassLoader();

看到一個class:

#!java
org.eclipse.jetty.webapp.WebAppClassLoader

jetty的漏洞,沒有tomcat那麼含蓄,非常直接的,context就掛載在classLoader上。

enter image description here

jetty在執行過程中,會去實時檢視ContextHandler中的shutdown屬性(webappcontext透過幾層繼承關係,繼承了這個類,其實親戚關係有八丈遠),一旦發現true,直接就不工作了,這是典型的,使用一個狀態判斷業務是否同行。基於這個原理,只要如下訪問,以後這個應用,就只剩下404了。

enter image description here

無論是什麼action,都只會返回這個錯誤,後續的執行,jetty都以為真的shutdown了。並且這個過程沒有任何補救措施,只能管理員手工重啟了,各位SA親們,你們準備好了麼?

jetty任意檔案下載

我們讓404為這個漏洞服務到底。

事實上,下面說的這個問題發生在jetty上,tomcat真的是巧合的逃過一劫。

我們看看jetty對於自定義錯誤檔案的配置過程:

enter image description here

這段配置檔案,可以自定義404錯誤檔案,這裡可以指定/WEB-INF/目錄下的檔案,一旦配置之後,由ErrorPageErrorHandler負責給errorPages(這也是個map)新增一個對應關係。這個類最終會被掛在到context中,那麼依照這個漏洞的原理,我們可以層層呼叫,最終制定一種錯誤,比如404錯誤。

Jetty把errorHandler掛載到context上,errorHander有個errorPages屬性,這其實是個map,代表錯誤頁面,key是一個返回碼數字,value就是錯誤後顯示的檔案。 所以開啟:

enter image description here

訪問圖片中這條URL後,效果如下,任何一個不存在的頁面都會顯示web.xml的內容:

enter image description here

有了這個問題,就可以讀取資料庫檔案,檢視資料庫密碼,讀取程式碼檔案,查詢隱藏的業務邏輯漏洞。注意,是任何人遇到404都可以看到這個頁面,最好等夜深人靜的時候再使用,用完了還得恢復原樣。

0x02 漏洞修補


這漏洞已經被官方修補了,2012年發出來的老問題,只是沒有單獨提交官方而已,居然也能拖到現在,建議各位下定決心換個框架。

from:http://security.alibaba.com/blog/blog_3.htm

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章