原来大多数程序员的SQL查询一直没用上参数绑定,这样安全吗

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

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

真的实现了参数绑定吗

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

<code>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
/<code>

我们使用的是beetl ORM框架,service层语句如下:

<code>public int getCount(String name) {
return userDao.getCountWithName(name);
}/<code>

控制层如下:

<code>@RequestMapping(value="/getCount")
public int getCount(String name){
return userService.getCount(name);
}/<code>

我们来运行一个查询,http://localhost:8080/users/getCount?name=chen

打开wireshark,来抓下包看看

原来大多数程序员的SQL查询一直没用上参数绑定,这样安全吗

SQL查询

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

<code>select count(*) from user where 1=1 and name = 'chen'/<code>

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

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

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

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

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

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

实现真正的参数绑定

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

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

我把配置文件改下,注意看圈红的部分

原来大多数程序员的SQL查询一直没用上参数绑定,这样安全吗

jdbc url

再抓下包看看

原来大多数程序员的SQL查询一直没用上参数绑定,这样安全吗

wireshark抓包MySQL参数绑定

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

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

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

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

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

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

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

<code>http://localhost:8080/users/getCount?name=chen' and salt='123/<code>

再经过Java的处理后,实际的查询是这样的

原来大多数程序员的SQL查询一直没用上参数绑定,这样安全吗

SQL注入

可以看出,并没有注入成功,被转义了。也就是说,你并不需要过分担心你的SQL安全。

总结

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

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

3.即使你不使用参数绑定,jdbc也会给你做客户端预编译来保证SQL安全。

4.如果使用原生JDBC查询,且没有使用PreparedStatement,默认配置下依然会发生SQL注入。

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

思考

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

答案是:只要数据库连接没使用GBK字符集,目前就是100%安全,否则会存在宽字节SQL注入。服务器端预编译才是100%安全。


分享到:


相關文章: