深入了解Java中的SQL注入
本文以代码实例复现了Java中JDBC及Mybatis框架采用预编译和非预编译时可能存在SQL注入的几种情况,并给予修复建议。
JDBC
首先看第一段代码,使用了远古时期的JDBC并且并没有使用预编译。这种简单的字符串拼接就存在SQL注入
@RequestMapping("/jdbc/vuln")publicStringjdbc_sqli_vul(@RequestParam("username")String username){StringBuilder result =newStringBuilder();try{Class.forName(driver);Connection con =DriverManager.getConnection(url, user, password);if(!con.isClosed())System.out.println("Connect to database successfully.");// sqli vuln codeStatement statement = con.createStatement();String sql ="select * from users where username = '"+ username +"'";
logger.info(sql);ResultSet rs = statement.executeQuery(sql);while(rs.next()){String res_name = rs.getString("username");String res_pwd = rs.getString("password");String info =String.format("%s: %s\n", res_name, res_pwd);
result.append(info);
logger.info(info);}
rs.close();
con.close();}catch(ClassNotFoundException e){
logger.error("Sorry,can`t find the Driver!");}catch(SQLException e){
logger.error(e.toString());}return result.toString();}
简单复现下
再看第二段代码,这段代码也是使用了JDBC但使用了PreparedStatement预编译,这回就避免了SQL注入
@RequestMapping("/jdbc/sec")publicStringjdbc_sqli_sec(@RequestParam("username")String username){StringBuilder result =newStringBuilder();try{Class.forName(driver);Connection con =DriverManager.getConnection(url, user, password);if(!con.isClosed())System.out.println("Connecting to Database successfully.");// fix codeString sql ="select * from users where username = ?";PreparedStatement st = con.prepareStatement(sql);
st.setString(1, username);
logger.info(st.toString());// sql after prepare statementResultSet rs = st.executeQuery();while(rs.next()){String res_name = rs.getString("username");String res_pwd = rs.getString("password");String info =String.format("%s: %s\n", res_name, res_pwd);
result.append(info);
logger.info(info);}
rs.close();
con.close();}catch(ClassNotFoundException e){
logger.error("Sorry, can`t find the Driver!");
e.printStackTrace();}catch(SQLException e){
logger.error(e.toString());}return result.toString();}
但是这种预编译在某些情况并不能使用
like模糊查询
例如在使用like进行模糊查询的时候,我们对第二段代码的sql进行修改
select*from users where username like'%?%'
预编译报错
order by
在order by的情况中也不能使用预编译,因为会将进行排序的字段名解析为字符串导致无法正常排序
若强行预编译
select*from users orderby ?
数据库数据如下
我想根据username进行排序则需要以下sql
select*from users orderby username
成功执行顺序是对的
但是强行预编译会将sql解析成
select*from users orderby'username'
排序错误导致失去作用
in
正常情况下的where in
select*from users where id in(1,2,3)
若强行预编译
select*from users where id in ?
导致结果报错
select*from users where id in'(1,2,3)'
mybatis
mybatis与hibernate等框架同样存在这些问题,hibernate现在很少用以mybatis为例
Mapper注解未使用占位符预编译存在SQL注入
@Select("select * from users where username = '${username}'")List<User>findByUserNameVuln01(@Param("username")String username);
Mapper xml未使用占位符预编译存在SQL注入
<selectid="findByUserNameVuln02"parameterType="String"resultMap="User">
select * from users where username like '%${_parameter}%'
</select>
Mapper xml在排序时未采用占位符预编译存在SQL注入
<selectid="findByUserNameVuln03"parameterType="String"resultMap="User">
select * from users
<iftest="order != null">
order by ${order} asc
</if></select>
安全的mybatis代码
@Select("select * from users where username = #{username}")UserfindByUserName(@Param("username")String username);
<selectid="findById"resultMap="User">
select * from users where id = #{id}
</select>
<selectid="OrderByUsername"resultMap="User">
select * from users order by id asc limit 1
</select>
修复
1.可以的话对参数进行类型转换,数字型注入很少出现大多都是字符串拼接
Interge.valueof()
2.占位符预编译,目前最有效的办法
3.like,order by,where in需要特殊处理
例如:
处理like
select*fromuserwhere username like concat('%', ?,'%')
处理order by
可以做白名单处理sql
/**
* 过滤mybatis中order by不能用#的情况。
* 严格限制用户输入只能包含<code>a-zA-Z0-9_-.</code>字符。
*
* @param sql sql
* @return 安全sql,否则返回null
*/privatestaticfinalPatternFILTER_PATTERN=Pattern.compile("^[a-zA-Z0-9_/\\.-]+$");publicstaticStringsqlFilter(String sql){if(!FILTER_PATTERN.matcher(sql).matches()){returnnull;}return sql;}
也可以在java层面做映射处理,例如限制用户输入1或2不同数字对应不同的排序
处理in
可以使用Mybatis自带循环指令解决SQL语句动态拼接的问题
select * from users where id in
<foreachcollection="ids"item="item"open="("separator=","close=")">
#{item}
</foreach>
SQL注入排查
可以使用专业的安全扫描工具也可以进行手动排查
关键字:Select、Dao 、from 、delete 、update、insert
版权归原作者 tpaer 所有, 如有侵权,请联系我们删除。