背景说明
渗透测试的时候,发现图片库的搜索框存在sql注入漏洞,攻击者利用该漏洞可执行’"<>&*等sql语句,造成数据库访问异常,甚至进行脱库
当我们拿着这段sql直接到数据库进行搜索的时候,会发现语法上就直接报错了
解决
先说结论:我们找到图片的mapper文件,排查sql语句后,将 ${ } 赋值改为 #{ } ,问题解决。如下:
sql语句优化前
<where>
<trim prefixOverrides = "AND | OR" suffixOverrides = ",">
<if test = "@Ognl@isNotEmpty(name)">
AND Name LIKE '%${name}%'
</if>
</where>
sql语句优化后
<where>
<trim prefixOverrides = "AND | OR" suffixOverrides = ",">
<if test = "@Ognl@isNotEmpty(name)">
AND Name LIKE '%#{name}%'
</if>
</where>
这里的name字段,在项目中是作为一个变量,后期动态赋值的。那说明进入到mysql里被执行的sql语句,就和开头一样,语法上就有错误。
而 #{ } 和 ${ } 方式都是后期动态赋值,为何 ${ } 会被sql注入呢?这里涉及不得不提到 # 和 $ 的两个知识点了
核心知识点:#和$的区别
mybatis 在对 sql 语句进行预编译之前,会对 sql 进行动态解析。
1、在这个解析阶段里:
#{ } 会被解析为一个 JDBC 预编译语句的占位符“?”传入mysql中。之后无论传什么值进去,在mysql中都只会被当做字符串,而不会被识别为sql语句;
${ }在这个阶段会进行纯粹的String变量替换,也就是传什么值,就替换为什么值,在预编译阶段,传到mysql中的就是一个完成的、已经赋值完成的sql语句。所以,如果传的值包含sql语句,到mysql中时则会被一并执行
2、如果采用#{ } 传值,那后续赋的值,mysql中会自动进行语法修正。(语法修正这一点,从控制台打印实际执行的sql语句就可以验证。在日志配置文件里,将log级别改为debug,即可打印实际执行的sql语句)
示例
给name字段传入一个值 Lucy # Mary
//1、#{ }方式
SELECT * FROM user WHERE name = #{name};
//sql解析后
SELECT * FROM user WHERE name = ?;
//赋值后,实际传到mysql中的sql语句
SELECT * FROM user WHERE name = 'Lucy # Mary';
//2、${ }方式
SELECT * FROM user WHERE name = ${name};
//sql解析后(直接替换变量值),实际传到mysql中的sql语句
SELECT * FROM user WHERE name = Lucy # Mary;
把这两条sql拿到数据库查询发现:${ }方式赋值后,#后面的内容都被注释掉了。
所以,大部分情况下,咱们都采用 #{ } 方式赋值,来避免sql注入的情况。
那什么时候用 ${ } 呢?
拓展
#和$还有一个小区别:
#{ }在sql解析时不是会被解析为一个标识符“?”吗,而 sql 占位符,替换为字符串时会带上单引号 ''
${ }在sql解析时进行的变量替换,就是个单纯的String替换,所以不会加单引号’’
从这个区别里,我们可以联想到,sql查询里,哪些查询不能加单引号呢?
这里提一个常见的:表名不能加单引号 ‘’(但着重号是可以的)。
从上图可以发现,表名加单引号会报错。
所以在写sql语句是,我们对表名的取值通常采用${ }方式。
SELECT * FROM ${tableName} WHERE name = #{name};
总结
1、sql动态解析阶段,#{ } 会被解析为一个 JDBC 预编译语句的占位符“?”,而${ }会进行纯粹的String变量替换
2、采用#{ } 传值,mysql中会自动进行语法修正
3、 #{ } 赋值为字符串时会带上单引号 ''
最后,附上参考文档,里面还有很多有趣的东西,可以帮助大家理解~