问题描述
@Select("select * from account order by #{orderBy} #{orderRule} limit #{start},#{offset}")
public List<Account> getAccountList(@Param("orderBy") String orderBy, @Param("orderRule") String orderRule,
@Param("start) int start, @Param("offset") int offset);
如上代码所示,在执行查询操作时,为了能够与前端联动进行排序,直接在SQL参数中传递排序字段和排序规则。
但是,在调试时偶然发现,当传递的“orderBy”值为不存在的字段时,竟然不会报错!!!
经过进一步调试发现,实际上并不会按照预期的排序规则返回数据列表!!!
原因追踪
设置log4j的日志级别为DEBUG后发现,最终执行的SQL语句是一个预编译操作,mybatis输出日志如下:
==> Preparing: select * from account order by ? ? limit ?, ?
==> Parameters: loginName(String), DESC(String), 0(Integer), 50(Integer)
很显然,传递的参数loginName和DESC是作为字符串处理的。
也就是说,很有可能mybatis对String类型的参数会进行转换。举个例子:传递的String参数为loginName,最终在SQL语句执行时为:\'loginName\'。
再进一步验证,如果在SQL语句中传递的排序字段不是字段名loginName,而是\'loginName\'时,是不会按照排序规则返回数据的,并且也不会报错!
追溯mybatis官方文档发现:默认情况下,使用#{}格式的语法会导致mybatis对字符串进行修改或转义!!!
详见:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html#select
解决问题
将参数传递的语法格式#{}修改为${},即:
@Select("select * from account order by ${orderBy} ${orderRule} limit #{start},#{offset}")
public List<Account> getAccountList(@Param("orderBy") String orderBy, @Param("orderRule") String orderRule,
@Param("start) int start, @Param("offset") int offset);
观察mybatis日志输出:
==> Preparing: select * from account order by login_name DESC limit ?, ?
==> Parameters: 0(Integer), 50(Integer)
此时,对于使用${}格式引用的参数,mybatis直接在SQL语句中插入一个不改变的字符串,而不再作为一个预编译参数处理。
注意: 以这种方式接收用户输入的内容并直接提供给SQL语句作为不变的字符串是不安全的,会导致潜在的SQL注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。
总结
如果只是想直接在SQL语句中插入一个不改变的字符串,比如,像ORDER BY,可以使用${}引用参数:ORDER BY ${columnName}
,这里mybatis不会修改或转义字符串。
值得注意的是:简单地将“#”修改为“$”,很容易带来SQL注入的问题,应该在Java层面应该做好参数检查,假定用户输入均为恶意输入,防范潜在的攻击,重点关注在三个方面“like”,“in”和“order by”。
【参考】
https://mp.weixin.qq.com/s/OTw3rCDWKD5cLqFLbdu_cg 本以为用的MyBatis框架就万无一失了,没想到还是被黑客注入了,我真的无语了!