用java PreparedStatement就不用担忧sql注入了吗?

     先感慨下,很久没写博客了,一是工做太忙,二是身体不太给力,好在终于查清病因了,趁着今天闲下来,火烧眉毛与读者交流,最后忠告一句:身体是活着的本钱!java

     言归正传,对java有了解的同窗基本上都体验过JDBC,基本都了解PreparedStatement,PreparedStatement相比Statement基本解决了SQL注入问题,并且效率也有必定提高。mysql

     关于PreparedStatement和Statement其余细节咱们不讨论,只关心注入问题。不管读者是老鸟仍是菜鸟,都须要问一下本身,PreparedStatement真的百分百防注入吗?sql

     接下来咱们研究一下PreparedStatement如何防止注入,本文以MySQL数据库为例。数据库

     为了不篇幅过长,我这里只贴代码片断,但愿读者能有必定的基础。搜索引擎

1 String sql = "select * from goods where min_name = ?";  // 含有参数
2 PreparedStatement st = conn.prepareStatement(sql);
3 st.setString(1, "儿童"); // 参数赋值
4 System.out.println(st.toString()); //com.mysql.jdbc.JDBC4PreparedStatement@d704f0: select * from goods where min_name = '儿童'

     这段代码属于JDBC常识了,就是简单的根据参数查询,看不出什么端倪,但假若有人使坏,想注入一下呢?spa

1 String sql = "select * from goods where min_name = ?";  // 含有参数
2 PreparedStatement st = conn.prepareStatement(sql);
3 st.setString(1, "儿童'"); // 参数赋值
4 System.out.println(st.toString()); //com.mysql.jdbc.JDBC4PreparedStatement@d704f0: select * from goods where min_name = '儿童\''

     简单的在参数后边加一个单引号,就能够快速判断是否能够进行SQL注入,这个百试百灵,若是有漏洞的话,通常会报错。code

     之因此PreparedStatement能防止注入,是由于它把单引号转义了,变成了\',这样一来,就没法截断SQL语句,进而没法拼接SQL语句,基本上没有办法注入了。blog

     因此,若是不用PreparedStatement,又想防止注入,最简单粗暴的办法就是过滤单引号,过滤以后,单纯从SQL的角度,没法进行任何注入。索引

     其实,刚刚咱们提到的是String参数类型的注入,大多数注入,仍是发生在数值类型上,幸运的是PreparedStatement为咱们提供了st.setInt(1, 999);这种数值参数赋值API,基本就避免了注入,由于若是用户输入的不是数值类型,类型转换的时候就报错了。接口

     好,如今读者已经了解PreparedStatement会对参数作转义,接下来再看个例子。

1 String sql = "select * from goods where min_name = ?";  // 含有参数
2 PreparedStatement st = conn.prepareStatement(sql);
3 st.setString(1, "儿童%"); // 参数赋值
4 System.out.println(st.toString()); //com.mysql.jdbc.JDBC4PreparedStatement@8543aa: select * from goods where min_name = '儿童%'

     咱们尝试输入了一个百分号,发现PreparedStatement居然没有转义,百分号刚好是like查询的通配符。

     正常状况下,like查询是这么写的:

1 String sql = "select * from goods where min_name like ?";  // 含有参数
2 st = conn.prepareStatement(sql);
3 st.setString(1, "儿童" + "%"); // 参数赋值
4 System.out.println(st.toString()); //com.mysql.jdbc.JDBC4PreparedStatement@8543aa: select * from goods where min_name like '儿童%'

     查询min_name字段以"儿童"开头的全部记录,其中"儿童"二字是用户输入的查询条件,百分号是咱们本身加的,怎么可能让用户输入百分号嘛!等等!若是用户很是聪明,偏要输入百分号呢?

String sql = "select * from goods where min_name like ?";  // 含有参数
st = conn.prepareStatement(sql);
st.setString(1, "%儿童%" + "%"); // 参数赋值
System.out.println(st.toString()); //com.mysql.jdbc.JDBC4PreparedStatement@8543aa: select * from goods where min_name like '%儿童%%'

     聪明的用户直接输入了"%儿童%",整个查询的意思就变了,变成包含查询。实际上不用这么麻烦,用户什么都不输入,或者只输入一个%,均可以改变原意。

     虽然此种SQL注入危害不大,但这种查询会耗尽系统资源,从而演化成拒绝服务攻击。

     那如何防范呢?笔者能想到的方案以下:

        

          ·直接拼接SQL语句,而后本身实现全部的转义操做。这种方法比较麻烦,并且极可能没有PreparedStatement作的好,形成其余更大的漏洞,不推荐。

          ·直接简单暴力的过滤掉%。笔者以为这方案不错,若是没有严格的限制,随便用户怎么输入,既然有限制了,就干脆严格一些,干脆不让用户搜索%,推荐。

        

     目前作搜索,只要不是太差的公司,通常都有本身的搜索引擎(例如著名的java开源搜索引擎solr),不多有在数据库中直接like的,笔者并非想在like上钻牛角尖,而是提醒读者善于思考,天天都在写着重复的代码,却历来没有停下脚步细细品味。

     有读者可能会问,为何咱们不能手动转义一下用户输入的%,其余的再交给PreparedStatement转义?这个留做思考题,动手试一下就知道为何了。

     注意,JDBC只是java定义的规范,能够理解成接口,每种数据库必须有本身的实现,实现以后通常叫作数据库驱动,本文所涉及的PreparedStatement,是由MySQL实现的,并非JDK实现的默认行为,也就是说,不一样的数据库表现不一样,不能一律而论。

相关文章
相关标签/搜索