在web项目中,有一个原则是永远不要相信从用户端传过来的不做处理的数据。
比如后端有个登陆接口;
@RequestMapping("/user/login") public String login(String name, String password) { String sql="select * from tp_user where name="+name+" and password ="+password; //执行sql 得到user User user = dao.excute(sql); //session或者分发token //..... return user==null?"登陆失败":"登陆成功"; }
这是一个很常见的登陆接口,用户会从表单里填入用户名和密码 后端根据用户名和密码去数据库查询 如果有该用户存在就登陆成功。这个逻辑乍一看没问题,但是如果前端填入的数据是这样的
@RequestMapping("/user/login") public String login(String name, String password) { name="admin or 1=1 -- "; password="1234"; String sql="select * from tp_user where name="+name+" and password ="+password;
User user = dao.excute(sql);
return user==null?"登陆失败":"登陆成功";
}
需要注意的是 此刻拼接而成的sql语句为
select * from tp_user where name=admin or 1=1 -- and password=1234
在sql语言里,--表示注释 会注释掉后面的语句,实际这句话为
select * from tp_user where name=admin or 1=1
这条语句只要表里有数据那就是百分百成功的,这只是伪装了登陆,如果用户传入的是“;drop table xx” 那么执行这条语句的后果可想而知。
针对sql注入,前端解决的方式则为数据格式正则校验,不允许使用特殊符号等等,后端则为预编译和参数化。
预编译的解释为 先预编译sql语句,使他的语义固定,即查询则为查询不允许执行其他操作,参数化则是将传过的值转义作为参数,比如上面的语句
name="admin or 1=1 -- "; password="1234"; String sql="select * from tp_user where name="+name+" and password ="+password; //未参数化 // "select * from tp_user where name=admin or 1=1 -- and password=1234" //参数化后则为 // "select * from tp_user where name= 'admin or 1=1 --' and 'password=1234'"
常见的持久层框架mybatis和hibernate中都有避免参数直接拼接语句的方法,比如mybatis中的#{}则是在任何参数上加一个' '。将传入的数据始终当成一个字符串。hibernate自写语句也有对应的占位符 通过修改占位符来参数化避免此问题。
sql注入说起来应该算是一种优点,是数据库保证有权限的用户有100%的掌控。在开发项目中应该对用户的数据进行校验实行白名单制(符合白名单正则的语句才可以执行) 或者使用持久层框架封装好的api。一定要对数据进行校验,不然可能会导致一些未知的错误(击穿,注入。。)。