PreparedStatement与SQL注入
什么是SQL注入
所谓SQL注入,就是通过把SQL命令插入到Web表单或输入域名或页面请求的查询字符串中,最终欺骗服务器执行的恶意的SQL命令。
举个简单的栗子
现在实现一个登陆功能,前台俩输入框,用户名和密码,后台获取到前台传来的用户名和密码后构建一条查询用户的sql语句:
1 | String sql = "select count(*) as count from user where username = " + username " and password = " + password; |
通过运行上面的sql语句,可以根据count是否大于0来判断用户是否可以正常登陆。
当然,这对于正常的登陆没有问题,但怕就怕不正常的情况。
假如,用户的密码是这样的''or '1'='1'
,那么这种情况下最终拼装出来的sql语句就是下面这条:
1 | select count(*) as count from user where username = 'name' and password = '' or '1' = '1'; |
这种情况下 sql语句返回的执行结果始终大于0,也就是说,可以通过这样的方式实现任意登录。之所以会出现这样的情况,是因为用户的输入中包含一些sql语句中特殊的单词或符号,而且后台把用户输入的东西在数据库执行了。
解决方法
我了解到的解决方法有下面三种:
- 对用户的输入进行过滤,转义敏感字符
- 使用存储过程,即sql语句都写在存储过程中,后天只是调用存储过程
- 使用PreparedStatement代替Statement
第一条:对用户的输入进行过滤,转义敏感字符
前台可以对用户的输入进行检测,如果出现像select/update/delete/>/
等sql关键字或操作符就提示为非法输入。
第二条:使用存储过程(这一条不说了,因为我暂时还没了解这么多)
第三条:使用PreparedStatement代替Statement
用PreparedStatement来是实现上面的登陆:
1 | Connection conn = DriverManger.getConnection("jdbc:mysql://ip:port/jdbc","name","password"); |
以上就是通过PreparedStatement实现登陆功能的伪代码。
用户输入内容相同的情况下,使用PreparedStatement打印出来的sql语句如下:
1 | select count(*) as count from user where username = 'name' and password ='\'\' or \'1\'=\'1\''; |
可以看到单引号被转义了,用户没法截断原来的字符串,所以也就没法sql注入,而且用户输入的密码完整的被当做了一个参数。
为什么使用PreparedStatement可以防止SQL注入
一个sql是经过解析器编译然后才执行。在使用Statement时,sql拼接完后才会进行编译然后执行,例如上面那句sql:
1 | select count(*) as count from user where username = 'name' and password = '' or '1 = 1'; |
这时候包括参数在内都会被编译然后执行,所以传入的参数中如果包含sql关键字或语句,就有可能出现sql注入。
使用PreparedStatement时,语句中的参数位置使用 ? 占位,在创建PreparedStatement对象时,sql语句就已经被编译,当所需要的参数传进来时语句就会直接执行。也就是说,预编译之后,数据库就不会重复编译了,这个时候再传什么and、or等关键字都只会当成普通字符串来处理,所以,即使用户传了能够引起sql注入的语句,也只会被当做参数,不会去运行。
究其原因就是PreparedStatement多了预编译的过程。