暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

使用PreparedStatement和ORM框架就能用上SQL预编译和参数绑定?

互联网活化石 2021-06-24
3057

开发WEB网站时,安全是个很重要的问题,其中最频繁被提到的一个问题就是SQL注入。SQL注入的原理我们就不多提了,但是提到防止SQL注入,我想很多人都会信誓旦旦地说:使用预编译和参数绑定啊,用了预编译就能防止SQL注入了。至于能不能防止SQL注入,那肯定是能的。


但是,你的SQL语句真的做了预编译参数绑定吗?


1

真的实现了参数绑定吗

我们以SpringBoot为例,配置文件如下:

    spring:
    profiles:
    active: dev
    datasource:
    type: com.zaxxer.hikari.HikariDataSource
    user: root
    password: 123456
    url: jdbc:mysql://172.17.0.2:3306/test?&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
    maxLifetime: 1765000 #一个连接的生命时长(毫秒)
    maximumPoolSize: 15 #连接池中允许的最大连接数
    pool-name: baPool
    connection-test-query: SELECT 1 FROM DUAL


    我们简单点,使用jdbcTemplate来查询:

      @RequestMapping("/users")
      @ResponseBody
      public List<Users> users(String name) {
      logger.info("使用JdbcTemplate查询数据库");
      String sql = "SELECT * FROM users where user_name=?";
      List<Users> queryAllList = jdbcTemplate.query(sql, new Object[]{name},
      new BeanPropertyRowMapper<>(Users.class));
      logger.info("查询用户列表" + queryAllList);
      return queryAllList;
      }

      我们来运行一个查询,
      http://10.180.205.89:9010/users?name=admin2

      打开wireshark,来抓下包看看

      可以看到,实际上执行的SQL语句是

        sSELECT * FROM users where user_name= 'admin2'

        结果是不是很出乎意料,这执行的就是普通的SQL拼接,根本没看到参数绑定啊!难道是jdbcTemplate这个框架的问题?我不是用了?来做参数绑定了吗?我没有拼接SQL啊


        换成Mybatis或者Hibernate又会怎样?你可以试一下,依然用不上参数绑定和预编译!那我用最原始的JDBC原生查询呢?比如类似这种写法呢:

          try (  因此需要在另一个方法中重新连接
          Connection conn = DriverManager.getConnection(url, user, pass);
          PreparedStatement pstmt = conn.prepareStatement("insert into student_table values(null, ?, 1)")
          ) {
          for (int i = 0; i < 100; i++) {
          pstmt.setString(1, "姓名" + i);
          pstmt.executeUpdate();
          }
          System.out.println("使用PreparedStatement耗时:" + (System.currentTimeMillis() - start));
          }

          看起来用了PreparedStatement,我要说的是,很遗憾,即使是明确使用PreparedStatement,Java里也不会做参数绑定,依然是拼接SQL查询。

          不是说拼接SQL查询有SQL注入风险吗?

          那要怎么做才能真正用到预编译和参数绑定呢?


          2

          实现真正的参数绑定

          关键在于JDBC URL上,我们需要加这个参数:

          useServerPrepStmts,只有这个参数为true时,才能做到MYSQL的参数绑定。

          我把配置文件改下,注意区别

                  url: jdbc:mysql://172.17.0.2:3306/test?&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&useServerPrepStmts=true
            driver-class-name: com.mysql.cj.jdbc.Driver


            再抓下包看看

            可以看出,现在才用了真正的参数绑定,查询语句中用了 ? 占位符号,分两次发送了模板和查询参数。


            实际上,JDBC预编译查询分为客户端预编译和服务器端预编译,对应的URL配置项是:useServerPrepStmts,当useServerPrepStmts为false时使用客户端(驱动包内完成SQL转义)预编译,useServerPrepStmts为true时使用数据库服务器端预编译。默认情况下,你使用的都只是客户端预编译。


            当然,客户端的参数绑定实际是就是SQL拼接和转义。


            客户端预编译实际上就是拼接SQL语句,但是拼接的同时,还对引号等特殊字符做了转义。


            也就是说,默认情况下,不管你使用了什么ORM框架,甚至是使用原生的JDBC PreparedStatement查询,都只是做的客户端预编译,而且肯定不会用上参数绑定!


            那客户端预编译和服务端预编译哪个更安全呢?那自然是服务端预编译!


            那为什么默认不使用服务端预编译呢?从抓包截图来看,服务端预编译执行了两次SQL查询,一次是发送模板,一次是发送参数,显然更耗费流量。幸运的是,同一个模板,在一个连接中只会发送一次。


            那么如果没开启这个服务端预编译,会不会带来安全隐患?我们试一下,写个带有SQL注入的查询:

              http://10.180.205.89:9010/users?name='admin2

              这里就不再截图了。实际上经过Java的处理后,最终的查询是被转义的。


              其实不止Java,其它语言如PHP,即使用了PDO,默认配置也不会使用预编译和参数绑定。


              3

              总结

              1. 不管你使用什么ORM框架或者原生JDBC查询,默认都不会使用SQL服务器自己的参数绑定


              2.要使用服务端参数绑定,必须在JDBC URL中明确指定useServerPrepStmts


              3.即使你不使用服务端参数绑定,各种jdbc框架默认配置下,如果你用了它们自己的参数绑定语法来做查询,就会给你做客户端预编译来保证SQL安全。


              4. 如果使用原生JDBC查询,且没有使用PreparedStatement,默认配置下会导致SQL注入。同理,如果你在各种ORM框架里自己手动拼接SQL,也会导致SQL注入。


              5.理论上,使用服务端预编译更安全,但会更耗流量。但是如果是对安全非常重视,建议你开启服务端预编译。


              4

              思考

              前面提到过,客户端预编译=拼接SQL语句+对引号等特殊字符做转义,并且说不需要担心SQL注入。那么没有使用参数绑定,仅仅靠拼接SQL注入和转义,真的能做到100%的安全吗?


              答案是:只要数据库连接没使用GBK字符集,目前就是100%安全,否则会存在宽字节SQL注入。当然,服务器端预编译是100%安全(不考虑0day或业务bug)。



              长按识别二维码  关注我们

              查看更多精彩内容


              文章转载自互联网活化石,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论