发布于12月5日12月5日 ## 1\\.问题背景 这篇文章源于一个生产问题。我们在生产环境的用户信息表的create\\_by字段中看到用户端的输入,并且没有过滤特殊字符(注意符号“+”的存在),我们怀疑它可能成为SQL注入点。 开发老大汇报,使用MyBatis-Plus来插入/更新该列的数据。但什么是MyBatis-Plus?以下引用自官方介绍: **什么是MyBatis-Plus?** MyBatis-Plus是MyBatis的一个强大的增强工具包,用于简化开发。这个工具包为MyBatis提供了一些高效、有用、开箱即用的功能,使用它可以有效节省你的开发时间。 那么,为MyBatis提供便捷实现的MyBatis-Plus是否可以在方便开发者的同时引入SQL注入呢?本文将从这个生产示例中回答以下问题: - MyBatis Plus如何防御SQL注入; ## 2\\.测试环境准备 为了不影响生产数据,使用官方提供的mybatis-plus-sample-crud快速搭建本地测试环境。 mysql版本:8.0.35 mybatis-plus-spring-boot3-starter 版本:3.5.4 ## 3\\.问题分析 ### **1) 确认生产数据来源** 根据表的“create\\_by”字段搜索源码发现使用了MyBatis Plus注解,它是基于iBatis实现的(具体参见第4节)。该字段来自用户传入的请求参数,没有经过任何过滤。 MemberInfo类定义了数据库字段create\\_by对应的属性: 查看参考。 InfoMessagesSAOImpl.java不涉及数据库操作。注意MemberServiceImpl.java的214行和1910行: mybatis-plus的insert接口分别在220/1915使用; 插入接口来自mybatisplus ### ** ** ### **2) 测试环境重现** 由于生产中使用的是insert方式,所以本地测试也使用insert,观察是否有注入。 测试数据表如下: 接下来,直接在mysql控制台中执行包含注入payload的语句: ```` 插入sys_user(id,姓名,年龄,电子邮件)值(124,'Jasdf',42,'[email protected]');/**/DROP/**/TABLE/**/sys_user;/**/--#',25,'[email protected]'); ```` 可以发现执行恶意语句并删除sys\\_user表: 重建表,将payload放入参数“name”中,观察MyBatisPlus如何处理恶意payload: ```` Jasdf',42,'[email protected]');/**/DROP/**/TABLE/**/sys_user;/**/--# `` 在Application.xml中添加配置并打印MyBatis-Plus构造的SQL语句,方便观察 ```` # MyBatis-Plus 配置 mybatis-plus: 配置: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #启用sql日志 ```` 注意,经过MyBatis-Plus处理后,payload仍然是String类型: Jasdf',42,'[email protected]');/\\*\\*/DROP/\\*\\*/TABLE/\\*\\*/sys\\_user;/\\*\\*/--# **(String)** 查询数据: Payload以完整的String形式插入到数据库中,说明注入不成功。 ## 4\\.防御方法分析 在第3节中,我们的注射不成功。下面通过调试来展示mybatis-plus对攻击者的payload做了什么。 在SqlSourceBuilder.java 中 在ParameterMappingTokenHandler 处理程序处放置一个断点。可以看到原来的语句首先被解析为模板+数据,其中模板为: \\`INSERT INTO sys\\_user ( id, name,age, email ) VALUES ( #{id}, #{name}, #{age}, #{email} )\\` 熟悉mybatis的同学都知道,相比于${value}不参与预编译的形式,#{value}形式的变量最终会调用preparedStatement来实现预编译。对于预编译的SQL语句,MySQL会使用开发者预先确定的参数类型构造SQL语句。换句话说,有效负载不会被理解为新的SQL 语句。 准备声明电话: 开始预编译: handler.parameterize 对预编译语句执行变量初始化。首先是long类型的id,通过ibatis的setLong方法进行处理(由于SQL注入主要针对String类型,本文不再进一步测试Int或long类型的处理方法,只关注String类型): 然后是String类型的名称: 模板SQL语句与boundSql对象中的参数对象一一对应 typeHandler.setParameter 绑定“名称”和有效负载 **来到关键方法。 ** Name为字符串类型,setNonNullParameter赋值,ps.setString(i,parameter);方法被调用。该方法判断String类型变量中的特殊符号是否需要转义: 可以发现,攻击载荷中包含的‘’在这里会被转义。另外,'\u0000',' ','\r',…等符号也会被转义,并在特殊符号后面酌情添加\', ,\r,\Zetc. 最后,经过预编译后,将发送到MySQL的stmt对象变为: ```` 插入sys_user ( id、姓名、年龄、电子邮件) 值( 1744564513751109634, 'Jasdf'',42,''[email protected]'');/**/DROP/**/TABLE/**/sys_user;/**/--#', 3, '[email protected]' ) ```` 比较原始有效负载: Jasdf',42,'[email protected]');/\\*\\*/DROP/\\*\\*/TABLE/\\*\\*/sys\\_user;/\\*\\*/--# Jasdf' **'**,42,''[email protected]' **'**);/\\*\\*/DROP/\\*\\*/TABLE/\\*\\*/sys\\_user;/\\*\\*/--# 回答第一个问题,String类型的payload经过preparedStatement处理后,会在原来的单引号'后面添加一个'作为转义符',这可能会导致SQL语句被截断,从而避免SQL注入的发生。本文以“insert”语句为例。 Update\Select\Create语句中的setString类型变量也是如此,这里不再赘述。 有这么方便的MyBatis-Plus,不需要人工干预就可以完成参数预处理。那么,只要使用它,是不是就能远离SQL注入,高枕无忧呢?其实是不可能的,我们下次再解释。 ## 5\\.参考 1. https://github.com/baomidou/mybatis-plus-samples 转载自freebuf:[https://www.freebuf.com/articles/web/389261.html](https://www.freebuf.com/articles/web/389261.html) 作者:FreeBuf_425926
创建帐户或登录后发表意见