SQL注入问题


一. Statement对象

  • 说明:SQL注入只对SQL语句的编译过程有破坏作用
  • 代码
public static void login(String username,String password){
    Statement st = conn.createStatement();
    String sql = "select * from user where name ='"+username+"' AND password = '"+ password+"'"; 
    ResultSet rs = st.executeQuery(sql); 
    // ...
}
  • 正确登录方式:login("admin","123456")
    • 对应SQL:select * from user where name ='admin' AND password= '123456'
  • SQL注入方式:login("'or '1=1","'or '1=1")
    • 对应SQL:select * from user where name = '' or '1=1' and password='' or '1=1'
    • 由于1=1永远成立,即where子句总为真

总结:Statement对象,默认直接执行SQL语句,对特殊字符不转义处理,导致SQL拼接出现漏洞。

二. preparedStatement对象

  1. 概述:preparedStatementStatement的子类,它会预编译SQL语句,使用?占位符代替参数,表示此处有待输入的变量,最后通过set方式来填值。该对象可以防止SQL注入,并且效率更高。
  2. 代码
public static void login(String username,String password){
    String sql = "select * from user where name =? AND password=?";
    PreparedStatement st = conn.prepareStatement(sql);    
    st.setString(1, username);  
    st.setString(2, password);
    ResultSet rs = st.executeQuery();
    //...
}

总结:preparedStatement把传递进来的参数当做字符串(使用引号包裹)。假设其中存在转义字符,比如说引号’'就会被直接转义,不再对sql语句进行解析编译,因此也就避免了sql注入问题。

三. MyBatis是否可以防止SQL注入

1. MyBatis中SQL注入的问题
  1. 示例代码
public interface UserDao{
    @Select("SELECT name,password FROM user WHERE name=${name}")
    List<User> getUserById(String name);
}

@Test
public void getUserById() {
    // 1.正常查询 
    //String name = "'admin'";
    // 2.SQL注入查询
    String name ="''or '1=1'";
    List<User> users = userMapper.getUserById(name);
    users.forEach(System.out::println);
}
  1. 执行的SQL语句
SELECT name,password FROM user WHERE name='admin';
SELECT name,password FROM user WHERE name=''or '1=1';

总结:在MyBatis中,${xxxx}格式的参数会直接参与SQL编译,从而不能避免注入攻击。

2. MyBatis中解决SQL注入

总结:把SQL语句中参数格式由 ${xxx} 变为 #{xxx},在MyBatis中可防止SQL注入。
原因:因为MyBatis启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,来替换占位符“?” ,这样就不会出现注入问题。
底层原理:在Mybatis框架底层,是JDBC中的PreparedStatement类在起作用。不仅能提高安全性,而且在多次执行同一个SQL时,使用已编译好的SQL能够提高效率。

  • 示例代码
@Select("SELECT name,password FROM user WHERE name=#{name}")
List<User> getUserById(String name);
}
  • 执行结果:不管输入任何参数,SQL语句都为SELECT name,password FROM user WHERE name=?。SQL语句中参数不会出现,而是使用?占位符代替。

三. 必须使用${}格式的情况

总结:在涉及到动态表名和列名时,必须使用${xxx}

  • 动态表名和列名的示例
select * from #{table} where #{column} =1;
  • 示例代码1:使用#{xxx},则有查询结果,但降序并没有生效。
@Select("select * from user order by #{id} desc")
List<User> getUserInfo(Long id);
  • 示例代码2:使用${xxx},则降序生效
@Select("select * from user order by ${id} desc")
List<User> getUserInfo(Long id);
  • 原因:如果使用#,是按string类型拼接,SQL语句变为order by 'id值' desc;如果使用$,则SQL语句为order by id desc

四. 总结

  • #{}:相当于JDBC中的PreparedStatement,会预编译;解析传过来参数带单引号;不存在SQL注入问题。
  • ${}:未经过预编译;参数直接填充到SQL中,即解析出来的参数值不带单引号;存在SQL注入问题。
  • 使用MyBatis框架编写SQL语句时,尽量采用#{xxx}格式来做参数的占位。
  • 在涉及到动态表名和列名时,只能使用${xxx}这样的参数格式占位。这种不得不使用的情况下,我们要手工做过滤工作,这样才能有效防止SQL注入攻击。

参考资料

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值