相比于ORM框架,Mybatis只能被称为半自动持久层框架,它其实是将JDBC进行了轻量级的封装,提供SQL映射能力,便于更为方便地管理项目中的SQL代码,同时避免程序员重复手动编写连接数据库底层代码,并提供数据缓存能力。
JDBC在使用时存在SQL注入攻击的风险,同样需要进行SQL编写的Mybatis同样也有这个问题,在使用时需要注意,防止被别有用心的人利用。
那么在Mybatis中如何避免SQL注入攻击呢?
答:在SQL映射文件中尽量使用#指示符标识参数位置,避免使用$。
我们通过简短的程序来看这二者实际有什么不同。
首先我们使用$符号来注入参数
<select id="getCustomerCar" resultType="map">
select * from tb_customer_car t where t.customer_id = ${customerId}
</select>
测试用的Java代码:
@Test
public void testGetCustomerCar(){
TestService service = new TestService();
try {
Map<String,Object> parameters = new HashMap<>();
//如果使用SQL拼接将or 1=1拼接进SQL,那么攻击者就能窃取到tb_customer_car表中的所有数据
parameters.put("customerId", "1 or 1=1");
service.getCustomerCar(parameters);
} catch (IOException e) {
e.printStackTrace();
}
}
程序输出结果:
==> Preparing: select * from tb_customer_car t where t.customer_id = 1 or 1=1
==> Parameters:
<== Columns: customer_id, brand, model
<== Row: 1, 福特, 福克斯
<== Row: 2, 雪佛兰, 科鲁兹
<== Total: 2
可以看到使用$指示符时Mybatis在编译时将1 or 1=1直接拼接进了SQL语句中,从而导致攻击者获取到所有的数据。
我们将$替换为#来注入参数:
<select id="getCustomerCar" resultType="map">
select * from tb_customer_car t where t.customer_id = #{customerId}
</select>
程序输出结果:
==> Preparing: select * from tb_customer_car t where t.customer_id = ?
==> Parameters: 1 or 1=1(String)
<== Columns: customer_id, brand, model
<== Row: 1, 福特, 福克斯
<== Total: 1
从输出结果可以看出,#标识的参数被预编译为了占位符?,1 or 1=1是作为参数来替换占位符。
这里查询出了customer_id为1的数据,是因为cutomer_id字段是int型的,在Mysql中将int类型数据和varchar类型数据比较判断时,会将varchar的首字符转化为数字来和int类型数据比较判断。
因此,为了防止SQL注入攻击,在使用Mybatis时我们应尽量使用#而不是$来注入参数。