Java代码审计(六)-若依管理系统V4.6.0
一、前言
若依管理系统是基于 SpringBoot 开发的轻量级 JAVA 开发框架,,本篇文章基于 V4.6.0 版本的基础上对部分历史漏洞进行审计分析。项目地址:https://gitee.com/y_project/RuoYi/tree/v4.6.0
二、环境搭建
这里依然使用 Phpstudy 作为数据库源,利用 navicat 导入数据表
创建数据库
导入数据表
IDEA导入项目源码,注意源码路径不要有中文,等待 Maven 下载项目依赖
我这边修改了几处,一为数据库账号密码及数据库名称(注意:修改为自己的本地数据库账号密码),二是将项目端口 80 改为 8888 ,路径改为了本机能访问的路径,因为本人环境是虚拟机,没有设置D盘
修改如下
点击运行项目,访问 http://localhost:8888/login 成功搭建,默认账号为 admin/admin123
三、代码审计
1、pom.xml 审计
老套路,存在 pom.xml,先审计其使用了哪些框架,框架组件是否存在历史漏洞。这个与以往的项目不一样的是,系统存在多个 pom.xml,我们只需要审计最外层的 pom.xml 即可
第三方组件如下,这一篇笔者不打算展开组件漏洞的审计,可自行根据版本网上 search 复现
1 | | 组件名称 | 组件版本 | |
2、漏洞审计
SQL注入
SpringBoot中 使用了 Mybatis 的 SQL 注入一般都是因为使用了 $ ,所以全局搜索 .xml 的 ${
。(注:搜索.xml文件的原因是该系统使用了 Mybatis 中的配置文件开发-》编写 xml 配置文件来映射相应的代码
)
这边先对第一个可疑点进行审计 ${params.dataScope}
因为该项目使用了 mybatis
并且利用了配置文件开发,所以会在 resource
目录下出现映射的 .xml
配置文件。我们在 SysRoleMapper.xml
文件中找到了可疑参数 ${params.dataScope}
,并定位到映射语句 id=selectRolelist
通过上面我们定位到文件 resources
目录下的 SysRoleMapper.xml
配置文件中的映射语句 id=selectRolelist
,根据以往文章的学习已知 resources
层对应 dao
层的接口,文件名也一致,所以我们定位到 dao
层的 SysRoleMapper.java
,找到 selectRolelist
接口
然后 Ctrl + 鼠标左键
跟进 selectRolelist
接口,看是谁调用这个接口。我们来到了 service
层的实体类 SysRoleServiceImpl.java
文件
然后我们将光标放到 selectRoleList
方法处,快捷键 Ctrl + u
进入该方法父类/接口定义的位置 Service 层的接口 ISysRoleService.java
(这里也可以直接鼠标右键方法名找到其调用链直接定位到 controller 层接口)
最后 Ctrl + 鼠标左键
,定位到具体的实现代码,由下图可知,59行、69行均有调用该函数
通过代码定位到 Controller
层的 SysRoleController.java
第59行 ,这个就是我们最终需要审计的代码,/list 有两个参数,SysRole 和 role ,最终 return 为 list ,而 List<SysRole> list = roleService.selectRoleList(role);
,所以参数值为 SysRole
参数定义的实体类
Ctrl + 鼠标左键
跟进 SysRole
参数,具体实体类如下,具体参数值为:roleId=&roleName=&roleKey=&roleSort=&dataScope=&status=&delFlag=&flag=&menuIds=&deptIds=
dataScope
参数没有过滤直接返回
尝试漏洞复现,定位路由从上到下 /system/role/list
且通过注解可知道 /list 为 POST 请求方式
直接后台浏览,找到 /system/role/list
路径
我们看到这里是没有 dataScope
参数的,我们自己加上即可
漏洞 payload: pageSize=&pageNum=&orderByColumn=&isAsc=&roleName=&roleKey=&status=¶ms[beginTime]=¶ms[endTime]=¶ms[dataScope]=and+updatexml(1,concat(0x7e,(SELECT+version()),0x7e),1)%2523
添加参数后,随便输入字符返回报错
成功查询版本信息,证明存在SQL注入
可直接发包到 sqlmap 跑数据
至此,selectRolelist
接口的SQL注入审计就结束了,整个审计流程为 SysRoleMapper.xml -》SysRoleMapper.jave -》SysRoleServiceImpI.java -》ISysRoleService.java -》SysRoleController.java -》SysRole.java
简单概况就是 ${ 定位漏洞点 -》resource 目录下的 Mybatis 映射语句配置文件 -》通过映射语句定位 Service 层的实体类接口 -》再定位到 Service 层的接口文件 -》通过接口定位到 Controller 层最终调用接口 -》最后定位到接口的具体实现代码 -》分析参数是否有过滤
我们亦可以通过对比官方更新文件找到漏洞点进行审计
修改前
修改后
Shiro组件漏洞
若依系统使用了 Shiro ,我们知道 Shiro < 1.2.4 存在反序列化漏洞, Shiro 1.4.2 到 1.8.0 存在权限绕过漏洞,本项目使用的 Shiro 版本为 1.7.0。
1)Shiro 反序列化
这里通过 Burp 插件找到了 Shiro 默认密钥 zSyK5Kp6PZAAjlT+eeNMlg==
这边使用 Github 上工具进行了复现,第一次尝试并没成功,后面换了一个工具,发现又可以了,所以平常在实战中遇到 Shiro Key 泄露的情况,多换几个工具尝试一下,没准会有收获
进行命令执行 python3 shiro-exploit.py echo -g CommonsCollectionsK1 -u http://192.168.114.160:8888/ -v 2 -k zSyK5Kp6PZAAjlT+eeNMlg== -c whoami
漏洞审计:
Shiro AES 秘钥在 1.2.4 版本及之前版本是存在密钥硬编码的,1.2.5 版本以后 Shiro 提供了 AES 密钥的随机生成代码,但如果仅进行 Shiro 版本升级,AES 密钥仍硬编码在代码中,仍然会存在反序列化风险。在审计中我们可以全局搜索 setCipherKey
,该方法是用于修改密钥的,若存在 setCipherKey
方法则说明存在默认key,进一步搜索 cipherKey
可查看具体密钥值
密钥硬编码在 application.yml
文件处
2)Shiro 权限绕过审计
此漏洞也只需要审计 ShiroConfig.java
配置文件里面的过滤器有没有对目录进行限制。由上一篇系列文章我们知道 anon
为匿名拦截器,不需要登录就能访问,一般用于静态资源,或者移动端接口;authc
为登录拦截器,需要登录认证才能访问的资源,需要在配置文件配置需要登录的 URL 路径
authc 拦截器匹配规则如下:
1 | | 通配符 | 说明 | |
以下静态资源进行匿名登录,不需要认证即可访问
对需要认证的页面进行了 /**
限制,需要登录认证才能访问
任意文件读取
若依系统任意读取文件影响版本为 RuoYi < v4.5.1
,本环境为 4.6.0
,这边重新部署了v4.5.0 的环境,学习该漏洞,部署方法如上。
通过对比 V4.5.0 与 V4.5.1 的代码分析定位漏洞点
V4.5.0 漏洞未修复前源码
V4.5.1漏洞修复后更新后的代码
根据官方代码可知漏洞位置为
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
由代码可知,在下载资源时没有任何的过滤,可直接下载本地资源
1 | /** |
审计初步请求路径为 /common/download/resource?resource=
,使用的方法为 GET(因为使用的注解是 @GetMapping)
本地资源路径为开始时我们设置的 uploadPath
路径,这段代码下载的文件就是这个路径下的文件
分析代码 String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
,其中 localPath
为本地资源路径 + StringUtils.substringAfter()
方法,其中方法中定义了请求参数为 resource
和请求前缀 Constants.RESOURCE_PREFIX
我们跟进 Constants.RESOURCE_PREFIX
Ctrl + 鼠标左键
查看文件下载资源时的请求前缀是什么,由下图可知,请求下载前的资源前缀为 /profile
downloadName = StringUtils.substringAfterLast(downloadPath, "/");
即下载名称为先执行 downloadPath
方法,再用 separator"/"
(分隔符+下载文件的名称)
整合上面分析的,得到整体路由为:/common/download/resource?resource=/profile/下载文件的名称
,使用方法为GET
漏洞复现
1、我们现在 localPath
即 C:/Tools/enviroment/RuoYi-v4.5.0/ruoyi/uploadPath
目录下创建一个文件用于测试
2、构造请求连接为 /common/download/resource?resource=/profile/flag.txt
,需在后台访问
3、由于代码没有对下载的路径进行限制,我们尝试利用目录穿越任意下载系统文件 windows/win.ini
,由下图可知,我们到达 C盘下的 windows/win.ini
有六层,所以我们需要六个 ../ 进行目录穿越
成功利用目录穿越漏洞实现任意文件下载
四、参考
1 | https://blog.csdn.net/qq_44029310/article/details/125296406 |