原來大多數程序員的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%安全。


分享到:


相關文章: