一、前言

之前我一直认为 Mybatis 框架下已经实现预编译机制,很多东西都封装好了,应该基本上不会再有 SQL 注入问题了。近期在渗透中发现,在实际项目中,即使使用了 Mybatis 框架,但仍然有可能因为编码人员安全意识不足而导致 SQL 注入问题。出现情况还真不少,因此有了这篇文章。

二、SQL 注入漏洞原理

1、概述

SQL 注入(SQL Injection)是发生在 Web 程序中数据库层的安全漏洞,是网站存在最多也是最简单的漏洞。主要原因是程序对用户输入数据的合法性没有判断和处理,导致攻击者可以在 Web 应用程序中事先定义好的 SQL 语句中添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步获取到数据信息。

简单地说,就是通过在用户可控参数中注入 SQL 语法,破坏原有 SQL 结构,达到编写程序时意料之外结果的攻击行为。其成因可以归结为如下原因造成的:

  1. 程序编写者在处理应用程序和数据库交互时,使用字符串拼接的方式构造 SQL 语句。
  2. 且未对用户可控参数进行足够的过滤。

2、漏洞复现

下面使用DVWA靶场来进行演示,网站架构为PHP,我们重点关注漏洞原理即可。

该页面提供了一个简单的查询功能,可以根据前端输入的用户ID来查询对应的用户信息。如图,输入 1,返回了对应 admin 用户的信息。

查看该页面的源代码:

<?php

if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ]; // Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"]; // Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
} mysqli_close($GLOBALS["___mysqli_ston"]);
} ?>

进行代码审计可以发现,程序将前端输入的 id 参数未加任何处理,直接拼接在了 SQL 语句中,那么此时就导致了SQL注入漏洞。

若此时攻击者输入的用户ID为 1' or 1='1,则程序拼接后执行的 SQL 语句变成了:

SELECT first_name, last_name FROM users WHERE user_id = '1' or 1='1';

可见,攻击者通过单引号 ' 闭合了数据库查询语句,并且在查询条件之后构造了“或 1=1”,即“或真”的逻辑,导致查询出了全部用户的数据。

如果攻击者可以任意替代提交的字符串,就可以利用 SQL 注入漏洞改变原有 SQL 语句的含义,进而执行任意 SQL 命令,入侵数据库进行脱库、删库,甚至通过数据库提权获取系统权限,造成不可估量的损失。(SQL注入的场景类型非常之多,攻击手法、绕过姿势也非常多,本文不作重点讨论)

3、修复建议

一般来说,防御 SQL 注入的最佳方式就是使用预编译语句(其他防御方法还有很多,本文不作重点讨论),绑定变量。例如:

String sql = "SELECT * FROM user_table WHERE username = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "zxd");
ResultSet results = pstmt.executeQuery();

使用预编译的 SQL 语句,SQL 语句的语义不会发生改变。在 SQL 语句中,变量用占位符 ? 表示,攻击者无法改变 SQL 的结构。

三、Mybatis 框架简介

1、参数符号的两种方式

Mybatis 的 SQL 语句可以基于注解的方式写在类方法上面,更多的是以 xml 的方式写到 xml 文件。Mybatis 中 SQL 语句需要我们自己手动编写或者用 generator 自动生成。编写 xml 文件时,MyBatis 支持两种参数符号,#{}${}

  • #{} 使用预编译,通过 PreparedStatement 和占位符来实现,会把参数部分用一个占位符 ? 替代,而后注入的参数将不会再进行 SQL 编译,而是当作字符串处理。可以有效避免 SQL 注入漏洞
  • ${} 表示使用拼接字符串,将接受到参数的内容不加任何修饰符拼接在 SQL 中。易导致 SQL 注入漏洞。

两者的区别如下:

  1. #{} 为参数占位符 ?,即 SQL 预编译。${} 为字符串替换,即 SQL 拼接。
  2. #{} 是“动态解析->预编译->执行”的过程。${} 是“动态解析->编译->执行”的过程。
  3. #{} 的变量替换是在 DBMS 中。${} 的变量替换是在 DBMS 外。
  4. 变量替换后,#{} 对应的变量自动加上引号。变量替换后,${} 对应的变量不会加上引号。

2、漏洞复现

下面以一个查询场景进行简单演示,数据库表 user_table 的表数据如下:

若没有采用 JDBC 的预编译模式,查询 SQL 写为:

<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username = '${username}'
</select>

这种写法就产生了 SQL 语句的动态拼接,这样格式的参数会直接参与 SQL 语句的编译,从而不能避免SQL注入攻击。

若此时攻击者提交的参数值为 zxd' or 1='1,如下图,利用 SQL 注入漏洞,成功查询了所有用户数据。

因此,应用 Mybatis 框架 SQL语句的安全写法(即 JDBC 预编译模式):

<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username = #{username}
</select>

可见,此时采用 JDBC 预编译模式,即使攻击者尝试 SQL 注入攻击,也只会将参数整体作为字符串处理,有效避免了 SQL 注入问题。

四、Mybatis 框架下的 SQL 注入问题及防护方法

还是以上节的查询场景举例,Mybatis 框架下易产生 SQL 注入漏洞的情况主要有以下三种:

1、模糊查询

在模糊查询场景下,考虑安全编码规范,使用 #{} 传入参数:

<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username like '%#{username}%'
</select>

在这种情况下使用 #{} 程序会报错:

于是很多安全经验不足的程序员就把 #{} 号改成了 ${},如果应用层代码没有对用户输入的内容做处理势必会产生SQL注入漏洞。

<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username like '%${username}%'
</select>

若此时攻击者提交的参数值为 zxd' or 1=1#,如下图,利用 SQL 注入漏洞,成功查询了所有用户数据。

因此,安全的写法应当使用 CONCAT 函数连接通配符

<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username like concat('%',#{username},'%')
</select>

2、带有 IN 谓词的查询

在 IN 关键字之后使用 #{} 查询多个参数:

<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username in (#{usernames})
</select>

正常提交查询参数 'zxd','hhh',因为预编译机制,系统将我们输入的字符当作了一个字符串,因此查询结果为空,不能满足业务功能需求。

于是很多安全经验不足的程序员就把 #{} 号改成了 ${}

<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username in (${usernames})
</select>

攻击者提交参数值 'hhh') or 1=1#,利用 SQL 注入漏洞,成功查询了所有用户数据。

因此,此种情况下,安全的做法应当使用 foreach 标签

<select id="getUserFromList" resultType="user.NewUserDO">
select * from user_table where username in
<foreach collection="list" item="username" open="(" separator="," close=")">
#{username}
</foreach>
</select>

3、带有动态排序功能的查询

动态排序功能,需要在 ORDER BY 之后传入参数,考虑安全编码规范,使用 #{} 传入参数:

<select id="getUserOrder" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table order by #{column} limit 0,1
</select>

提交参数 username 根据用户名字段排序。但因为预编译机制,系统将我们输入的字符当作了一个字符串,根据字符串排序是不生效的,不能满足业务功能需求。(根据用户名字段排序,此时正常应返回 root 用户)

于是很多安全经验不足的程序员就把 #{} 号改成了 ${}

<select id="getUserOrder" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table order by ${column} limit 0,1
</select>

攻击者提交参数值 username#,利用 SQL 注入漏洞,成功查询了所有用户数据。

因此,此种情况下,安全的做法应当在 Java 代码层面来进行解决。可以设置一个字段值的白名单,仅允许用户传入白名单内的字段。

String sort = request.getParameter("sort");
String[] sortWhiteList = {"id", "username", "password"};
if(!Arrays.asList(sortWhiteList).contains(sort)){
sort = "id";
}

或者仅允许用户传入索引值,代码再将索引值映射成对应字段。

String sort = request.getParameter("sort");
switch(sort){
case "1":
sort = "id";
break;
case "2":
sort = "username";
break;
case "3":
sort = "password";
break;
default:
sort = "id";
break;
}

需要注意的是在 mybatis-generator 自动生成的 SQL 语句中,ORDER BY 使用的也是 ${},而 LIKE 和 IN 没有问题。

Mybatis下的SQL注入漏洞原理及防护方法的更多相关文章

  1. SQL注入漏洞原理

    系统中安全性是非常重要的,为了保证安全性很多解决方案被应用到系统中,比如架设防火墙防止数据库服务器直接暴露给外部访问者.使用数据库的授权机制防止未授权的用户访问数据库,这些解决方案可以很大程度上避免了 ...

  2. 【漏洞复现】CVE-2022–21661 WordPress核心框架WP_Query SQL注入漏洞原理分析与复现

    影响版本 wordpress < 5.8.3 分析 参考:https://blog.csdn.net/qq_46717339/article/details/122431779 在 5.8.3 ...

  3. Mybatis下的sql注入

    以前只知道mybatis框架下,order by后面接的是列名是不能用#{},这样不起效果,只能用${},这样的话就可能产生sql注入.后来发现其实还有另外两种情况也是类似的: 1.order by ...

  4. 简单说说mybatis是防止SQL注入的原理

    mybatis是如何防止SQL注入的 1.首先看一下下面两个sql语句的区别: <select id="selectByNameAndPassword" parameterT ...

  5. 关于ECSHOP中sql注入漏洞修复

    标签:ecshop sql注入漏洞修复 公司部署了一个ecshop网站用于做网上商城使用,部署在阿里云服务器上,第二天收到阿里云控制台发来的告警信息,发现ecshop网站目录下文件sql注入漏洞以及程 ...

  6. SQL注入漏洞的原理

    在平常生活中,我们登陆某网页,常常需要输入用户名和密码,点击登陆,即可登陆成功. 对于黑客来说,不需要用户名和密码,只输入 admin '— 也可以登陆成功. 黑客利用的这种漏洞就是SQL Injec ...

  7. 怎样判断有没有SQL注入漏洞及原理?

    来源:实验楼 最为经典的单引号判断法: 在参数后面加上单引号,比如: http://xxx/abc.php?id=1' 如果页面返回错误,则存在 Sql 注入. 原因是无论字符型还是整型都会因为单引号 ...

  8. 管中窥豹——框架下的SQL注入 Java篇

    管中窥豹--框架下的SQL注入 Java篇 背景 SQL注入漏洞应该算是很有年代感的漏洞了,但是现在依然活跃在各大漏洞榜单中,究其原因还是数据和代码的问题. SQL 语句在DBMS系统中作为表达式被解 ...

  9. 10年前,我就用 SQL注入漏洞黑了学校网站

    我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...

  10. 从c#角度看万能密码SQL注入漏洞

    以前学习渗透时,虽然也玩过万能密码SQL注入漏洞登陆网站后台,但仅仅会用,并不理解其原理. 今天学习c#数据库这一块,正好学到了这方面的知识,才明白原来是怎么回事. 众所周知的万能密码SQL注入漏洞, ...

随机推荐

  1. KingbaseESV8R3对于order by null列的处理

    背景 客户遇到一个现象order by 列是null的时候,最后一行总是显示相同的值.不要问我为什么对空值排序,意义何在?客户代码如此,客户不愿意改代码,作为dba大家都懂的.客户认为即使对null排 ...

  2. Web开发框架『express』的基本使用 —— { }

    基本 res.send([body]) 和 res.end([data] [, encoding]) 的区别 1.参数的区别: res.send([body]): body这个参数可以是[Buffer ...

  3. 日志:Redo Log 和 Undo Log

    本篇文章主要介绍 Redo Log 和 Undo Log: 利用 Redo Log 和 Undo Log 实现本地事务的原子性.持久性 Redo Log 的写回策略 Redo Log Buffer 的 ...

  4. 使用Elasticsearch Operator快速部署Elasticsearch集群

    转载自:https://www.qikqiak.com/post/elastic-cloud-on-k8s/ 随着 kubernetes 的快速发展,很多应用都在往 kubernetes 上面迁移,现 ...

  5. 使用KubeOperator安装k8s集群后,节点主机yaml文件路径

    [root@k8s-develop-master-1 kubernetes]# cd /etc/kubernetes [root@k8s-develop-master-1 kubernetes]# l ...

  6. 为不同的用户生成不同的 Kibana 界面

    文件转载自:https://elasticstack.blog.csdn.net/article/details/109593613

  7. 2_CSS

    1. 什么是CSS 1.1 什么是CSS Cascading Style Sheet 层叠样式表 是一种用来表现HTML(标准通用标记语言的一个应用)或XML(标准通用标记语言的一个子集)等文件样式的 ...

  8. MySQL用户也可以是个角色

    前言 角色(Role),可以认为是一些权限的集合,一直是存在各个数据库中,比如Oracle.SQL Server.OceanBase等,MySQL 自从 8.0 release 才引入角色这个概念. ...

  9. POJ2686 Traveling by Stagecoach (状压DP)

    将车票的使用情况用二进制表示状态,对其进行转移即可. 但是我一开始写的代码是错误的(注释部分),看似思路是正确的,但是暗藏很大的问题. 枚举S,我们要求解的是dp[S][v],这个是从u转移过来的,不 ...

  10. 洛谷P1115 最大子段和 (线性DP)

    经典的线性DP例题,用f[i]表示以第i个位置结尾的最大连续子段和. 状态转移方程:f[i]=max(f[i],f[i-1]+a[i]); 这里省去了a数组,直接用f数组读数据,如果f[i-1]< ...