定时任务功能点绕过黑白名单执行任意sql语句
2022-8-24 08:1:31 Author: 橘猫学安全(查看原文) 阅读量:58 收藏

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

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

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();
}
}

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

定时任务执行逻辑

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

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

excute方法如下:

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

继续跟进invokeMethod(Job)方法


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

一些细节处:
获取BeanName

获取MethodName

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

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


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

接着便走到如下分支

从上可分析出如下结果:

  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


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

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

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

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

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

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

寻找spring容器中可利用对象

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

  1. 对象存在于spring容器中

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

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

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

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

    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编码:

设置变量,值为hex编码:

定义预处理语句:

执行预处理语句:

数据成功插入:

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

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

选中立即执行

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

如有侵权,请联系删除

推荐阅读

实战|记一次奇妙的文件上传getshell

「 超详细 | 分享 」手把手教你如何进行内网渗透

神兵利器 | siusiu-渗透工具管理套件

一款功能全面的XSS扫描器

实战 | 一次利用哥斯拉马绕过宝塔waf

BurpCrypto: 万能网站密码爆破测试工具

快速筛选真实IP并整理为C段 -- 棱眼

自动探测端口顺便爆破工具t14m4t

渗透工具|无状态子域名爆破工具(1秒扫160万个子域)

查看更多精彩内容,还请关注橘猫学安全:

每日坚持学习与分享,觉得文章对你有帮助可在底部给点个“再看

文章来源: http://mp.weixin.qq.com/s?__biz=Mzg5OTY2NjUxMw==&mid=2247497446&idx=3&sn=32aa4529f172b67b8ccc0d6a7cf4b15d&chksm=c04d71d8f73af8cea9940f947dd50ddd3c73e577156215c5f4ba0d45db8b67d79bc4395c8f0d#rd
如有侵权请联系:admin#unsafe.sh