这套系统有个老版本,部署特别多,所以挖这类目标的时候第一件事就是判断版本。框架本身一直在更新,但真正跑在公网上的,大量还是停留在两三年以前的版本,那批版本里的问题基本是明牌。
2.1 先看懂攻击链路:请求进来之后发生了什么
一套ruoyi后端,典型的请求链路是:Nginx -> Spring Boot应用 -> Shiro/Spring Security过滤器链 -> Controller -> Service -> Mapper(MyBatis)-> MySQL/Redis等存储组件。要挖漏洞,就要在链路的每一环找"可被操纵"的点。
有相当一部分注意力放在权限控制层。老版本用的是Apache Shiro,新版本里有Spring Security版本,两者的鉴权机制差别很大。Shiro那条线,核心在ShiroConfig里配置的匿名URL和过滤器链顺序;Spring Security那条线,核心在SecurityConfig里对/login、/register、验证码接口这些"白名单"路径的放行方式。这里经常出现的低级问题就是:开发为了让某个接口不被拦截,直接加到anonymous或permitAll列表里,结果这个接口本身是敏感接口(比如查询全部用户、修改配置)。
还有一种很容易被忽视的情况是拦截器/注解的覆盖范围不一致。框架里接口权限用的是@PreAuthorize("@ss.hasPermi('system:user:list')")这类注解,但Controller层如果漏写注解,而全局配置又没有兜底策略,这个接口就变成了"登录即可访问",甚至"未登录即可访问"。我在实际测试里见过不少这类案例,业务方为了省事,在自定义Controller里直接调用SysUserService的敏感方法,却没加任何权限注解。
2.2 核心组件的老化程度:Shiro之外的"定时炸弹"
除开鉴权组件,还有几个组件的高频问题值得关注:
Quartz定时任务:框架自带"定时任务"管理页面,允许管理员配置cron表达式、调用目标bean和方法。核心风险在于,任务调用的目标是反射拼接的,如果某个版本的配置校验不够严格,就可以"调用到"一些危险类的危险方法。虽然这个功能需要管理员权限,但配合越权或弱口令,就能形成完整攻击链。更重要的是,Quartz本身还涉及反序列化问题(
org.quartz.impl.jdbcjobstore),如果用了JDBCJobStore,且库里的QRTZ_JOB_DETAILS表能被通过SQL注入等方式写入恶意序列化数据,那也是RCE,不过这条链路的利用门槛比Shiro反序列化高不少。Druid监控页面:框架带Druid数据库连接池,
druid.stat.view.servlet如果暴露出去,未授权就能看到SQL监控、Session监控等敏感信息,有时候还能直接看到内网地址。老版本里默认路径是/druid,且部分部署连登录页都没配置。这不算多高级的漏洞,但在SRC上报里属于有效信息,CNVD也认。Redis:新版配置里Redis密码是硬编码在
application-druid.yml里的,如果Redis端口暴露到公网、密码是弱口令,攻击者可以通过Redis写文件或者Spring Session反序列化,进一步控制应用。这类问题在部署不规范的目标里很常见,虽然根因不在ruoyi框架本身,但框架默认给你配好了,用的人没有安全意识,就白给了。
2.3 代码生成器与在线用户管理:被人忽视的"信息泄露口"
框架自带的代码生成器(tool/gen相关接口,在老版本里是/tool/gen,新版是/tool/gen基础上加了权限)在默认情况下需要管理员权限,但如果版本较老或权限校验缺失,就可能未授权访问,直接把数据表的字段结构、注释、实体类代码全部暴露出来。数据库表结构本身就是很有价值的信息,表字段就是攻击者的地图。
在线用户管理(/monitor/online)会展示当前登录用户的Session ID和登录IP,如果这个接口未授权可访问,攻击者可以直接强退他人会话,甚至结合Session固定思路做进一步利用。
这些点都不是"拿shell"级别的漏洞,但它们是漏洞挖掘里很好的"敲门砖",能帮你快速积累对目标系统的认知,判断后续往哪个方向深挖。
3. 五类高频漏洞形态与触发逻辑
下面结合我在授权测试和个人研究里的实际观察,梳理五类在ruoyi里反复出现的高频漏洞。每一类我都说一下触发逻辑,尽量不写具体利用代码,重点讲怎么发现和判断,因为防御方更需要的是"识别"能力。
3.1 反序列化:Shiro默认密钥与硬编码AES Key
老版本ruoyi使用Shiro,而Shiro的"RememberMe"功能使用了AES-CBC加密序列化数据,密钥写在ShiroConfig里。框架历史上有一段时间用的是官方文档里的默认值fCq+/xW489hGYDVHRVFcNQ==(不同版本有不同默认值),很多部署根本没改。攻击者只要用这个密钥自己构造恶意序列化对象,加密后放进Cookie,服务器就会在反序列化时执行命令。
这个漏洞的检测很简单:请求/login时往Cookie里塞一个用默认密钥加密的序列化对象,观察响应是否异常。修复方案就是生成随机Key,升级Shiro版本(新版框架已移除Shiro)。我在实际测试中遇到最难缠的情况是:目标改了默认Key,但只改了这一个Key,org.apache.shiro:shiro-core里的其他反序列化gadget链仍然存在,只是没了入口。这种"改了一半"的情况很常见。
新版框架如果迁移到了Spring Security,这类漏洞就自然消失了,但对应地会出现Spring Security自身的配置风险,比如HttpSecurity.authorizeRequests()规则写错。
3.2 定时任务:表达式注入与危险调用
Quartz定时任务模块在框架里叫"定时任务",页面里可以新建任务,填写"调用目标"(比如ryTask.ryParams)和cron表达式。框架做了很多安全过滤,比如限制包名前缀、校验cron表达式等,但版本的演进过程中,都有对应的绕过方式。
我印象比较深的是某个版本里,调用目标的类名过滤是基于黑名单的,把javax.naming、org.apache.commons.collections这些常见的危险包给拦了,但漏了JDK自带的com.sun.rowset.JdbcRowSetImpl(JNDI注入),配合Spring Boot内置的Tomcat,可以走ELProcessor表达式执行。修复方案也很简单:把"调用目标"的解析改成白名单,只允许调用预设的Service方法。
从漏洞挖掘角度看,这类型问题需要注意的前提是:定时任务管理页面本身需要管理员权限。所以绝大多数情况下,它的利用路径是"先拿到管理员会话,再通过定时任务getshell"或者"存在越权,普通用户也能访问该接口"。如果目标系统能未授权直接访问定时任务页面,那价值就非常高了。
3.3 文件上传:后缀校验与路径穿越的"组合拳"
框架自带的文件上传一般走/common/upload,存储路径在配置文件里。这类漏洞的核心难点不在于"能不能传",而在于"传上去之后能不能被解析成代码"。漏洞挖掘里常见的问题是:
- 校验只防了后缀,没防
Content-Type,或者校验了后缀但没防大小写(.JSP、.PhP在被解析时可能因为Tomcat配置而被识别为jsp)。 - 上传接口对路径参数校验不严,存在路径穿越,可以把文件写到
webapps/ROOT外的任意目录。 - 上传后文件名用了原始文件名,导致恶意文件直接落在静态资源目录下可访问。
我在给一套自建系统做授权测试时,遇到过ruoyi的上传接口本身没什么问题,但业务方在代码里自定义了一个上传接口,直接copy框架代码,把校验逻辑删得只剩if (file.isEmpty()),这就全白给了。
3.4 SQL注入:别只盯着MyBatis的${}
MyBatis框架本身对#{}做了预编译,所以注入点往往在${}拼接的地方,比如排序字段、表名、IN语句参数、LIKE模糊搜索的拼接逻辑。ruoyi的BaseEntity里有params字段,常用作查询条件,比如params[beginTime]、params[dataScope],如果开发图省事直接在Mapper.xml里做了${params.dataScope}拼接,立刻就是SQL注入。
还有一类容易被忽略的是**druid监控页面的SQL日志**,它本身不执行SQL,但会展示连接池执行过的SQL文本,如果页面未授权访问且前端没有对拼接内容做转义,攻击者可以通过构造畸形参数让SQL日志里出现特殊内容,虽然这不算SQL执行漏洞,但会泄露业务数据接口。
3.5 越权与未授权:低危名称下的高影响力
框架的自带功能里,我建议重点看两个方向:
一是IDOR/水平越权。框架很多实体都继承了BaseEntity,主键用的是自增ID或雪花ID,自定义接口经常直接用前端传的ID去查询。比如/system/notice/notice/1可以查公告,那改成/2、/3呢?业务方如果不做数据权限过滤,就是水平越权。
二是垂直越权/未授权访问。框架提供了一堆/monitor/*、/tool/*开头的接口,有些版本在配置匿名URL时把整个/monitor/**放行了,里面的/monitor/online、/monitor/cache都能未授权访问。这些信息看似不痛不痒,但能帮助进一步攻击。
4. 授权范围内从零到一的完整挖掘流程
接下来按实操顺序,把整个挖掘流程走一遍。前提:所有操作都在授权范围内进行,最好有书面授权书,遵守《网络安全法》及相关法规。
4.1 指纹识别:先确认它确实是ruoyi
第一步永远是判断目标是不是ruoyi。有几种可靠的识别方式:
- 访问
/login页面,看HTML源码里有没有ry-logo、vue项目编译后的资源路径、#/login这类特征。 - 请求
/prod-api/前缀(新版前后端分离默认路由前缀)下的接口,看响应头或错误信息。 - 直接访问
/druid/index.html,如果返回200,基本实锤是Java系且大概率ruoyi。 - 登录页面的"验证码"接口:
GET /captchaImage返回一张base64图片和一个uuid,这也是ruoyi的特征。 - 查看
/favicon.ico的MD5(如果没被改),网上已经有ruoyi默认图标的Hash库。
判断完版本范围之后,再去做下一步。
4.2 API路径测绘:把路由面拉出来
老版本的前端页面和后端接口是分离的,接口文档在/swagger-ui.html或/doc.html(knife4j)。如果这两个页面能访问,那接口列表直接是白给,下面的步骤都可以简化。
如果文档被关闭了,可以按下面的方式来测绘:
- 先确定接口前缀:新版是
/prod-api,老版本有的用/,有的自己配了server.servlet.context-path。 - 使用常规的轻量级目录扫描工具(比如
dirsearch/ffuf),字典里重点覆盖/system/user/list、/system/role/list、/tool/gen/list、/monitor/online/list、/common/download等框架常用路径。 - 拦截器在响应header里可能泄露
X-Requested-With: XMLHttpRequest,也可作为判断接口是否为异步请求的辅助特征。
测绘过程中我给个建议:结合前端JS文件一起看。登录后把static/js目录下的app.js、chunk-*.js拉下来,直接搜url:后面的字符串,能拿到一版"被注释掉的、被隐藏起来的"接口列表,这是纯黑盒目录扫描找不到的。
4.3 验证与利用:从"可能"到"确定"
一个可疑接口确认漏洞,通常需要三次请求:
- 未登录请求:确认返回码(若返回
401或302说明有鉴权;若返回200且带业务数据,说明疑似未授权)。 - 带普通用户Token请求:确认水平越权或垂直越权。
- 针对具体漏洞类型的有效载荷请求:比如SQL注入就构造成
1 and 1=1与1 and 1=2看响应差异。
这里要特别强调证据留存。完整记录请求包、响应包、时间戳,这些是写报告、提交SRC时必须的材料。CNVD和SRC平台很看重可复现性,你只写"存在SQL注入"是没有分量的,得附上请求包、注入点、影响的表或payload示例(注意脱敏)。
4.4 报告撰写:决定漏洞能否落地
报告的价值占整个漏洞挖掘工作的一半。一份好报告要讲清楚:
- 漏洞URL、参数、请求方法。
- 影响的系统版本与组件版本,便于修复方定位代码。
- 完整的复现步骤(截图或请求包)。
- 危害说明:不浮夸,也不贬低。
- 修复建议:尽量具体(比如"升级xx版本至x.x.x""在ShiroConfig中将密钥改为随机值""修改Druid监控页访问权限")。
提交SRC时,漏洞等级判定主要看利用成本和影响范围。同样是Shiro反序列化,如果目标在内网、无法访问,分值和可利用性都有限;如果直接暴露公网且能出网,那就是严重。
5. 我在实战中踩过的坑:给你提个醒
这部分内容是纯经验,不写网上到处都是的东西,只讲那些绕了很多弯路才明白的事情。
5.1 版本差异导致的"假阳性"与"假阴性"
有一次,我测试一个目标时,用老版本Shiro利用链打过去,发现Cookie反序列化没有任何反应。我判断目标没有漏洞,但后来仔细翻包,发现目标根本没有优先加载Shiro的过滤器,而是先被前端网关拦截了。也就是说,目标系统可能套了一层其他代理,我的payload根本没到后端。这种情况下,报告写"否"就漏报了。
反过来,也有"假阳性":用扫描器扫出来某个接口"疑似SQL注入",但其实是该接口对畸形输入做了统一异常处理,返回固定错误页面。所以,所有自动扫描结果都必须手工复核,这一步绝对不能省。
5.2 开发环境与生产环境的"版本漂移"
源码审计和在线测试对不上号的情况非常多。一套生产系统,代码基于上游ruoyi二开,但二开时把shiro.ini、application.yml改了很多,甚至把某个功能模块从下游框架里抽出来重写了。这就导致你在Gitee上看的源码与目标实际情况完全两样。要避免这个问题,尽量以在线行为观测为准,源码分析只作为辅助判断。
5.3 授权边界与测试范围:保护自己才是第一要务
很多做漏洞挖掘的新人容易忽略:授权书里写的范围是哪个域名、哪些IP段、允许的时间窗口。有的目标因为业务联动,会导致你访问到第三方系统(比如CDN、OSS、第三方登录),这些一旦越界,麻烦非常大。我在实际操作中,会用浏览器插件加一个"当前测试目标"的提示条,看清域名才发请求,同时所有非授权目标一律不碰。
5.4 别把"框架漏洞"和"业务漏洞"混为一谈
提交漏洞时,尽量区分"框架自身问题"和"业务方二开引入的问题"。这两个的修复责任方不同:前者是升级框架、打补丁;后者是业务方在私有代码里改逻辑。有些SRC平台因为目标绑定的主体不同,对两类漏洞的确认和打分差异很大。我在报告里会明确写"该问题在框架默认配置下即存在"或"该问题由业务方自定义上传逻辑导致",这样审核方省力,自己也免得被驳回。
6. 从防御视角看:部署一套"不好打"的ruoyi要做哪些事
如果你刚好是使用这套框架的运维或开发,最后这部分是写给你们的。我在前面挖了这么多窟窿,现在反过来给加固建议。
6.1 版本升级是第一优先级
框架的Gitee官方仓库持续在更新,去掉了很多已知问题。别再守着老版本了。升级之前重点看两个东西:数据库结构变化和配置项变化。老版本升到新版,不只是替换jar包,数据库里的sys_config、sys_menu可能会有增量数据,菜单权限表也可能会变化,升级前做好备份和测试环境演练。
6.2 组件密钥与配置项的收敛
逐一检查这些配置项:
ShiroConfig里的AES密钥:如果用老版本,改成随机生成,长度至少16字节。- Redis密码:不要留空,不要用
123456,最好用带特殊字符的长密码。 druid.stat.view.servlet.enabled:如果不需要监控页,直接设为false;需要的话加账号密码和IP白名单。application.yml里的server.servlet.session.timeout:不要太长,降低会话固定风险。
6.3 网关/WAF层的防护
不要只依赖框架自身的过滤器,在Nginx层把该禁的路径先禁了:
- 对
/druid/**、/swagger-ui.html、/v2/api-docs、/doc.html做访问限制或直接拒绝。 - 自定义业务上传接口时,尽量用对象存储(OSS等),别把文件落在本地web目录。
- 配置限流,防止登录接口被爆破;登录接口加验证码,框架自带这个功能,一定要开启。
6.4 日志与监控
ruoyi自带/monitor/operlog和/monitor/logininfor,能记录操作日志和登录日志。开启后能让你在攻击发生后的很短时间内发现问题。我在一次授权测试里,刚扫完接口列表就在/monitor/operlog里看到了红字告警,那是业务方自己接的日志系统弹出来的,识别速度很快,这也能看出来日志真正发挥作用时的价值。
另外建议在Nginx层加一个“访问频率异常”的告警规则,很多自动化攻击的特征就是短时间内大量请求不同的URL,及时感知就能在漏洞被利用前切掉入口。
写在最后:把这套流程变成你自己的方法论
这篇内容没有详细展开具体某个漏洞的完整利用路径,因为公开环境下讨论利用细节的尺度需要克制,而且脱离了授权场景讨论利用本身没有意义。我更希望传递的是一套“如何在合规前提下,把一个开源框架的系统漏洞挖清楚、讲明白”的流程和思维。
我自己在多年的测试里反复体会到的几件事,拿出来共勉:
- 漏洞挖掘不是比谁工具多、跑的字典大,而是比谁对系统原理理解更深。把框架生命周期、组件调用链、鉴权模型的底层逻辑吃透,很多“看着没有漏洞”的系统,其实到处是破绽。
- 授权是底线,报告是门面。一个漏洞无论多简单,只要在授权范围内、报告写得清晰规范,它就是有价值的成果;反之,没有授权的漏洞挖掘,哪怕技术再炫,也是害人害己。
- 多关注上游更新和社区的通报,很多漏洞不是“挖出来的”,是“等出来的”。框架每次发版修复的问题,往往就预示着同一个问题在所有运行实例里都还存在,只是还没有人公开利用而已。
如果你正准备入门漏洞挖掘,拿ruoyi这套框架练手是很好的选择:源码开放、案例丰富、社区活跃,可以同时练习代码审计和黑盒测试。但记得在自己搭的靶场环境里练,或者只针对有授权的目标,安全这条红线什么时候都不能越。