先感慨下,很久沒寫博客了,一是工做太忙,二是身體不太給力,好在終於查清病因了,趁着今天閒下來,火燒眉毛與讀者交流,最後忠告一句:身體是活着的本錢!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實現的默認行爲,也就是說,不一樣的數據庫表現不一樣,不能一律而論。