每天資訊Java程式碼審計之SpEL表示式注入

菜單

Java程式碼審計之SpEL表示式注入

*本文原創作者:Lateink,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載

SpEL 表示式注入

Spring Expression Language(簡稱 SpEL)是一種功能強大的表示式語言、用於在執行時查詢和操作物件圖;語法上類似於 Unified EL,但提供了更多的特性,特別是方法呼叫和基本字串模板函式。SpEL 的誕生是為了給 Spring 社群提供一種能夠與 Spring 生態系統所有產品無縫對接,能提供一站式支援的表示式語言。

SpEL 表示式

基本表示式

字面量表達式、關係,邏輯與算數運算表示式、字串連結及擷取表示式、三目運算、正則表示式以及括號優先順序表示式;

類相關表示式

類型別表示式、類例項化、instanceof 表示式、變數定義及引用、賦值表示式、自定義函式、物件屬性存取及安全導航表示式、物件方法呼叫、Bean 引用;

集合相關表示式

內聯 List、內聯陣列、集合、字典訪問、列表、字典;

其他表示式

模版表示式

SpEL 基礎

在 pom。xml 匯入 maven 或是把”org。springframework。expression-3。0。5。RELEASE。jar”新增到類路徑中

5。0。8。RELEASE org。springframework spring-expression ${org。springframework。version}

SpEL 使用方式

SpEL 在求表示式值時一般分為四步,其中第三步可選:首先構造一個解析器,其次解析器解析字串表示式,在此構造上下文,最後根據上下文得到表示式運算後的值。

ExpressionParser parser = new SpelExpressionParser; Expression expression = parser。parseExpression(“(‘Hello’ + ‘ freebuf’)。concat(#end)”); EvaluationContext context = new StandardEvaluationContext; context。setVariable(“end”, “!”); System。out。println(expression。getValue(context));

1。建立解析器:

SpEL 使用 ExpressionParser 介面表示解析器,提供 SpelExpressionParser 預設實現;

2。解析表示式:使用 ExpressionParser 的 parseExpression 來解析相應的表示式為 Expression 物件。

3。構造上下文:準備比如變數定義等等表示式需要的上下文資料。

4。求值:透過 Expression 介面的 getValue 方法根據上下文獲得表示式值。

SpEL 主要介面

1。

ExpressionParser 介面

:表示解析器,預設實現是 org。springframework。expression。spel。standard 包中的 SpelExpressionParser 類,使用 parseExpression 方法將字串表示式轉換為 Expression 物件,對於 ParserContext 介面用於定義字串表示式是不是模板,及模板開始與結束字元;

public interface ExpressionParser { Expression parseExpression(String expressionString); Expression parseExpression(String expressionString, ParserContext context); }

事例 demo

ExpressionParser parser = new SpelExpressionParser; ParserContext parserContext = new ParserContext { @Override public boolean isTemplate { return true; } @Override public String getExpressionPrefix { return “#{”; } @Override public String getExpressionSuffix { return “}”; } }; String template = “#{‘hello ’}#{‘freebuf!’}”; Expression expression = parser。parseExpression(template, parserContext); System。out。println(expression。getValue);

演示的是使用 ParserContext 的情況,此處定義了 ParserContext 實現:定義表示式是模組,表示式字首為「#{」,字尾為「}」;使用 parseExpression 解析時傳入的模板必須以「#{」開頭,以「}」結尾。

預設傳入的字串表示式不是模板形式,如之前演示的 Hello World。

EvaluationContext 介面

:表示上下文環境,預設實現是 org。springframework。expression。spel。support 包中的 StandardEvaluationContext 類,使用 setRootObject 方法來設定根物件,使用 setVariable 方法來註冊自定義變數,使用 registerFunction 來註冊自定義函式等等。

Expression 介面

:表示表示式物件,預設實現是 org。springframework。expression。spel。standard 包中的 SpelExpression,提供 getValue 方法用於獲取表示式值,提供 setValue 方法用於設定物件值。

SpEL 語法 – 類相關表示式

類型別表示式:

使用”T(Type)”來表示 java.lang.Class 例項,”Type”必須是類全限定名,”java.lang”包除外,即該包下的類可以不指定包名;使用類型別表示式還可以進行訪問類靜態方法及類靜態欄位。

具體使用方法

ExpressionParser parser = new SpelExpressionParser; // java。lang 包類訪問 Class result1 = parser。parseExpression(“T(String)”)。getValue(Class。class); System。out。println(result1); //其他包類訪問 String expression2 = “T(java。lang。Runtime)。getRuntime。exec(‘open /Applications/Calculator。app’)”; Class result2 = parser。parseExpression(expression2)。getValue(Class。class); System。out。println(result2); //類靜態欄位訪問 int result3 = parser。parseExpression(“T(Integer)。MAX_VALUE”)。getValue(int。class); System。out。println(result3); //類靜態方法呼叫 int result4 = parser。parseExpression(“T(Integer)。parseInt(‘1’)”)。getValue(int。class); System。out。println(result4);

類例項化:類例項化同樣使用 java 關鍵字「new」,類名必須是全限定名,但 java。lang 包內的型別除外,如 String、Integer。

instanceof 表示式:SpEL 支援 instanceof 運算子,跟 Java 內使用同義;如”‘haha’ instanceof T(String)”將返回 true。

變數定義以及引用:變數定義透過 EvaluationContext 介面的 setVariable(variableName, value) 方法定義;在表示式中使用”#variableName”引用;除了引用自定義變數,SpE 還允許引用根物件及當前上下文物件,使用”#root”引用根物件,使用”#this”引用當前上下文物件;

自定義函式:目前只支援類靜態方法註冊為自定義函式;SpEL 使用 StandardEvaluationContext 的 registerFunction 方法進行註冊自定義函式,其實完全可以使用 setVariable 代替,兩者其實本質是一樣的

審計過程

這裡拿 Spring Message 遠端命令執行漏洞來作為例子

環境搭建

git clone https://github。com/spring-guides/gs-messaging-stomp-websocket git checkout 6958af0b02bf05282673826b73cd7a85e84c12d3

拿到專案程式碼,全域性搜尋一下 org。springframework。expression。spel。standard,發現 DefaultSubionRegistry。java 檔案處有匯入。

Java程式碼審計之SpEL表示式注入

再搜尋一下 SpelExpressionParser

Java程式碼審計之SpEL表示式注入

往下跟進發現如下關鍵程式碼,具體分析看程式碼註釋

@Override protected void addSubionInternal( String sessionId, String subsId, String destination, Message message) { Expression expression = null; MessageHeaders headers = message。getHeaders; // 這裡可以看出 SpEL 表示式 expression 是從 headers 中的 selector 欄位中取出來 String selector = SimpMessageHeaderAccessor。getFirstNativeHeader(getSelectorHeaderName, headers); if (selector != null) { try { //生成 expression 物件 expression = this。expressionParser。parseExpression(selector); this。selectorHeaderInUse = true; if (logger。isTraceEnabled) { logger。trace(“Subion selector: [” + selector + “]”); } } catch (Throwable ex) { if (logger。isDebugEnabled) { logger。debug(“Failed to parse selector: ” + selector, ex); } } } // expression 傳入 addSubion 這個函數里面,即存放在 this。subionRegistry this。subionRegistry。addSubion(sessionId, subsId, destination, expression); this。destinationCache。updateAfterNewSubion(destination, sessionId, subsId); }

再搜尋一下 this。subionRegistry,看看有沒有呼叫傳進去的 expression。

然後發現了!

Java程式碼審計之SpEL表示式注入

在這裡呼叫了 this。subionRegistry。getSubions(sessionId) 並從中取出 info->sub-> expression。

最關鍵的是,這裡直接呼叫了 expression。getValue!這說明如果能控制 SpEL 的表示式,就能直接命令執行!

再來看看這個 filterSubions 函式在哪裡呼叫。從函式的呼叫回溯追蹤呼叫鏈如下:

filterSubions -> findSubionsInternal -> findSubions -> sendMessageToSubscribers

sendMessageToSubscribers 即傳送訊息的功能

回顧一下整個流程,SpEL 表示式從 headers 中 selector 獲取,即傳送請求時新增 selector 到請求的 header 即可傳入,然後生成 expression 物件傳入 this。subionRegistry,然後當傳送訊息的時候,最終會直接從 this。subionRegistry 取出並呼叫 expression。getValue 執行我們傳入的 SpEL 表示式。

驗證過程,在 expression。getValue 這裡打個斷點,看看傳送訊息是否會攔截並檢視呼叫鏈是否如上述分析一樣。

Java程式碼審計之SpEL表示式注入

Bingo!

簡單總結一下 SpEL 表示式注入的分析思路,可以先全域性搜尋 org。springframework。expression。spel。standard, 或是 expression。getValue、expression。setValue,定位到具體漏洞程式碼,再分析傳入的引數能不能利用,最後再追蹤引數來源,看看是否可控。Spring Data Commons Remote Code Execution 的 SpEL 注入導致的程式碼執行同樣可以用類似的思路分析。

漏洞修復

SimpleEvaluationContext、StandardEvaluationContext 是 SpEL 提供的兩個 EvaluationContext

SimpleEvaluationContext - 針對不需要 SpEL 語言語法的全部範圍並且應該受到有意限制的表示式類別,公開 Spal 語言特性和配置選項的子集。

StandardEvaluationContext - 公開全套 SpEL 語言功能和配置選項。您可以使用它來指定預設的根物件並配置每個可用的評估相關策略。

SimpleEvaluationContext 旨在僅支援 SpEL 語言語法的一個子集。它不包括 Java 型別引用,建構函式和 bean 引用;所以最直接的修復方式是使用 SimpleEvaluationContext 替換 StandardEvaluationContext。

這是我個人學習程式碼審計過程中的小總結,可能邏輯性相對來說沒那麼嚴謹,但是個人覺得這是一個比較通俗易懂的分析方法,不喜勿噴。

參考文章:

*本文原創作者:Lateink,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載