定时任务功能点绕过黑白名单执行任意sql语句

admin 2022年5月18日11:20:29评论436 views字数 4616阅读15分23秒阅读模式

定时任务功能点绕过黑白名单执行任意sql语句

环境

若依后台管理系统存在多种架构体系。如下

定时任务功能点绕过黑白名单执行任意sql语句

这里使用RuoYi-fast v4.7.3(前后端不分离)来分析定时任务功能点处如何绕过黑白名单,执行任意的sql语句

Quartz组件

RuoYi-fast使用Quartz作为定时任务组件,但由于本文是重点分析定时任务处产生漏洞原因,因此仅简单写个Quartz使用demo,更多的使用可百度获取。
创建springboot项目,导入如下依赖:

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
</dependencies>

编写job,需要继承org.quartz.Job,如下继承org.quartz.Job抽象子类QuartzJobBean

package com.example.quartz.job;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateTimeJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
String msg = (String) context.getJobDetail().getJobDataMap().get("msg");
System.out.println("current time :"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "---" + msg);
}
}

配置jobtrigger

package com.example.quartz.config;

import com.example.quartz.job.DateTimeJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuartzConfig {
// 配置 job
@Bean
public JobDetail printTimeJobDetail(){
return JobBuilder.newJob(DateTimeJob.class)//PrintTimeJob我们的业务类
.withIdentity("DateTimeJob")//可以给该JobDetail起一个id
//每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中可以通过context获取
.usingJobData("msg", "Hello Quartz")//关联键值对,当触发定时任务时,可从上下文中获取此键值对
.storeDurably()//即使没有Trigger关联时,也不需要删除该JobDetail
.build();
}

// 配置 trigger:触发规则
@Bean
public Trigger printTimeJobTrigger() {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/1 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(printTimeJobDetail())//关联上述的JobDetail
.withIdentity("quartzTaskService")//给Trigger起个名字
.withSchedule(cronScheduleBuilder)
.build();
}
}

启动项目,定时任务成功执行

定时任务功能点绕过黑白名单执行任意sql语句

漏洞分析

定时任务执行逻辑

定时任务代码在com.ruoyi.project.monitor.job包下:
Job接口是真正干活的,所要实现的业务处理,都会继承此接口重写excute方法
RuoYi-fast中定义的Job体系如下:

定时任务功能点绕过黑白名单执行任意sql语句

查看AbstractQuartzJob抽象类,新增了beforedoExecuteafter方法,重写了父类的excute方法(此方法采用了模版方法设计模式),其中before方法记录任务执行开始时间,doExecute是真正执行任务的方法(此方法交给具体的子类实现),after方法用于将任务执行日志写入数据库中,excute方法将before、doExecute、after三个方法组合。

定时任务功能点绕过黑白名单执行任意sql语句

excute方法如下:

定时任务功能点绕过黑白名单执行任意sql语句

子类QuartzDisallowConcurrentExecutionQuartzJobExecution实现了doExecute方法,这两个类只存在再并发支持上的区别,因此这里分析QuartzJobExecution即可。

定时任务功能点绕过黑白名单执行任意sql语句

继续跟进invokeMethod(Job)方法

定时任务功能点绕过黑白名单执行任意sql语句


跟进invokeMethod方法,利用反射执行方法

定时任务功能点绕过黑白名单执行任意sql语句

一些细节处:
获取BeanName

定时任务功能点绕过黑白名单执行任意sql语句

获取MethodName

定时任务功能点绕过黑白名单执行任意sql语句

获取MethodParams:从目标字符串中提取第一个(和第一个)中间的字符串。并将其以,进行分割成字符串数据

定时任务功能点绕过黑白名单执行任意sql语句

遍历分割生成的字符串数组

定时任务功能点绕过黑白名单执行任意sql语句


可以看到参数类型仅仅支持以下数据类型:StringBooleanLongDoubleInteger类型。
判断beanName是否是指定格式,当beanName是中不包括.或者仅仅包括一个.符号便符合指定格式

定时任务功能点绕过黑白名单执行任意sql语句

接着便走到如下分支

定时任务功能点绕过黑白名单执行任意sql语句

定时任务功能点绕过黑白名单执行任意sql语句

从上可分析出如下结果:

  1. 对象可以是spring容器中注册过的bean,也可以指定class名称

  2. 若是spring容器中注册过的bean,则可直接从spring容器中取出,若是指定class名称,则可以通过反射newInstance()创建对象,因此必须保证class中存在无参构造函数

  3. 方法不能是private修饰的方法,因为在getDeclaredMethod获取方法后,并没有执行setAccessible(true)

  4. 方法参数类型仅仅可以为如下类型:String,Boolean,Long,Double,Integer

定时任务添加/修改逻辑

由于定时任务的新增和修改逻辑相似,因此这里仅仅分析定时任务的添加。
查看:com.ruoyi.project.monitor.job.controller.JobController#addSave

定时任务功能点绕过黑白名单执行任意sql语句


可以看到在添加定时任务前,进行了黑白名单的判断。当通过了上述条件后,则执行com.ruoyi.project.monitor.job.service.JobServiceImpl#insertJob,代码如下,先将定时任务相关字段存入数据库中,然后使用Quartz创建定时任务

定时任务功能点绕过黑白名单执行任意sql语句

跟进com.ruoyi.project.monitor.job.util.ScheduleUtils#createScheduleJob,创建定时任务

定时任务功能点绕过黑白名单执行任意sql语句

成功创建定时任务后,便可等待任务触发或者立即执行,便会走到上一节<<定时任务执行逻辑>>中代码
然后再回来看看黑白名单:
黑名单:

定时任务功能点绕过黑白名单执行任意sql语句

白名单:
虽然有个白名单,不但一个有趣的现象是,RuoYi官方在对黑白名单进行判断的时候,存在遗漏,导致漏洞利用。
代码如下:

定时任务功能点绕过黑白名单执行任意sql语句

红框中的代码很眼熟,就是如下代码

定时任务功能点绕过黑白名单执行任意sql语句

定时任务功能点绕过黑白名单执行任意sql语句

可以发现代码仅仅对全限定类名(class)进行了检测。但是没有对spring容器中的对象进行白名单检测。
只需要在spring容器中找到一个可以利用的对象,即可以绕过黑名单检测,又可以逃过白名单的检测。

寻找spring容器中可利用对象

<<定时任务执行逻辑>>小结已经分析出,定时任务若是调用spring容器中的对象,则需要满足如下条件:

  1. 对象存在于spring容器中

  2. 方法至少不能是private修饰的方法

  3. 参数类型只能为StringBooleanLongDoubleInteger类型
    利用上述条件刷选方法,看是否存在可以利用的方法。可以编写脚本进行筛选,也可以借助spring actuator手动筛选,actuator组件会列举spring容器中所有的对象。但由于RuoYi-fast没有使用actuator组件,这里简单添加一下依赖和配置即可


    定时任务功能点绕过黑白名单执行任意sql语句

    访问Beans接口,获取所有的spring beans对象。可以手工一个一个去IDEA中去搜索类,查找符合的方法。

    定时任务功能点绕过黑白名单执行任意sql语句

    JdbcTemplate类中,发现executeupdate方法(public方法),参数为String,符合前面分析的所需要的条件。


    定时任务功能点绕过黑白名单执行任意sql语句

    execute方法可以执行任意sql语句。不过在截取方法参数值时,是从目标字符串中提取第一个(和第一个)中间的字符串,若目标字符串为:jdbcTemplate.execute("insert into sys_user_role values(7,7);"),则方法参数值为"insert into sys_user_role values(7,7,具体可在<<定时任务执行逻辑>>节中找到代码论证。下一节分析如何绕过这个截取方式

使用预处理和hex编码绕过对方法参数的截取

方法参数值是通过从目标字符串第一个(和第一个)中间的字符串获取的,那么只要保证参数值内容中不出现)即可。
可以使用mysql预处理和hex编码使参数值内容中不出现)。可以参考2019年的强网杯【随便注】一题。
比如,以执行insert into sys_user_role values(7,7);为例。
首先将insert语句进行hex编码:

定时任务功能点绕过黑白名单执行任意sql语句

设置变量,值为hex编码:

定时任务功能点绕过黑白名单执行任意sql语句

定义预处理语句:

定时任务功能点绕过黑白名单执行任意sql语句

执行预处理语句:

定时任务功能点绕过黑白名单执行任意sql语句

数据成功插入:

定时任务功能点绕过黑白名单执行任意sql语句

依次将上述sql语句填入jdbcTemplate.execute方法中,如下payload中没有)符号。并且每次修改定时任务时,立即执行任务。

定时任务功能点绕过黑白名单执行任意sql语句

在后台修改任务:目标字符串依次如上

定时任务功能点绕过黑白名单执行任意sql语句

选中立即执行

定时任务功能点绕过黑白名单执行任意sql语句

观察数据库,成功执行insert语句

定时任务功能点绕过黑白名单执行任意sql语句

漏洞修复

定时任务功能点绕过黑白名单执行任意sql语句

该内容转载自网络,更多内容请点击“阅读原文”

定时任务功能点绕过黑白名单执行任意sql语句

原文始发于微信公众号(web安全工具库):定时任务功能点绕过黑白名单执行任意sql语句

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月18日11:20:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   定时任务功能点绕过黑白名单执行任意sql语句http://cn-sec.com/archives/1017612.html

发表评论

匿名网友 填写信息