sql注入及用PrepareStatement就不用擔心sql注入了嗎?

首先講一下sql注入

所謂SQL注入,就是通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務器執行惡意的SQL命令。具體來說,它是利用現有應用程序,將(惡意)的SQL命令注入到後臺數據庫引擎執行的能力,它可以通過在Web表單中輸入(惡意)SQL語句得到一個存在安全漏洞的網站上的數據庫,而不是按照設計者意圖去執行SQL語句。比如先前的很多影視網站泄露VIP會員密碼大多就是通過WEB表單遞交查詢字符暴出的,這類表單特別容易受到sql注入攻擊。
驗證繞過漏洞就是'or'='or'後臺繞過漏洞,利用的就是AND和OR的運算規則,從而造成後臺腳本邏輯性錯誤。
先舉個例子,你要登錄一個網站,上面讓你輸入用戶名字和密碼。那麼,假如你輸入的用戶名是 admin ,但是你不知道密碼,你就輸入了一個 1' OR '1' = '1 ,那麼,你就提交了兩個參數給服務器。假如,服務器拿這兩個參數拼SQL語句:SELECT T.* FROM XXX_TABLE TWHERE T.USER_ID = '/*param1*/'AND T.PASSWORD = '/*param2*/'那麼,你提交的兩個參數就使SQL文變成了:SELECT T.* FROM XXX_TABLE TWHERE T.USER_ID = 'admin'AND T.PASSWORD = '1' OR '1' = '1'那麼,這個SQL原來的校驗功能就被你繞過去了,你的這種行爲就稱之爲SQL注入。

接下來用PrepareStatement就不用擔心sql注入了嗎?

下面內容轉載自https://www.cnblogs.com/iyangyuan/archive/2015/09/15/4809494.html
 言歸正傳,對java有了解的同學基本上都體驗過JDBC,基本都瞭解PreparedStatement,PreparedStatement相比Statement基本解決了SQL注入問題,而且效率也有一定提升。
     關於PreparedStatement和Statement其他細節我們不討論,只關心注入問題。無論讀者是老鳥還是菜鳥,都需要問一下自己,PreparedStatement真的百分百防注入嗎?
     接下來我們研究一下PreparedStatement如何防止注入,本文以MySQL數據庫爲例。
     爲了避免篇幅過長,我這裏只貼代碼片段,希望讀者能有一定的基礎。
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, "兒童"); // 參數賦值
System.out.println(st.toString()); 
//com.mysql.jdbc.JDBC4PreparedStatement@d704f0: 
select * from goods where min_name = '兒童'
這段代碼屬於JDBC常識了,就是簡單的根據參數查詢,看不出什麼端倪,但假如有人使壞,想注入一下呢?
String sql = "select * from goods where min_name = ?";  // 含有參數
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, "兒童'"); // 參數賦值
System.out.println(st.toString());
 //com.mysql.jdbc.JDBC4PreparedStatement@d704f0: 
select * from goods where min_name = '兒童\''

 簡單的在參數後邊加一個單引號,就可以快速判斷是否可以進行SQL注入,這個百試百靈,如果有漏洞的話,一般會報錯。
     之所以PreparedStatement能防止注入,是因爲它把單引號轉義了,變成了\',這樣一來,就無法截斷SQL語句,進而無法拼接SQL語句,基本上沒有辦法注入了。
     所以,如果不用PreparedStatement,又想防止注入,最簡單粗暴的辦法就是過濾單引號,過濾之後,單純從SQL的角度,無法進行任何注入。
     其實,剛剛我們提到的是String參數類型的注入,大多數注入,還是發生在數值類型上,幸運的是PreparedStatement爲我們提供了st.setInt(1, 999);這種數值參數賦值API,基本就避免了注入,因爲如果用戶輸入的不是數值類型,類型轉換的時候就報錯了。
     好,現在讀者已經瞭解PreparedStatement會對參數做轉義,接下來再看個例子。
String sql = "select * from goods where min_name = ?";  // 含有參數
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, "兒童%"); // 參數賦值
System.out.println(st.toString()); 
//com.mysql.jdbc.JDBC4PreparedStatement@8543aa: 
select * from goods where min_name = '兒童%'
 我們嘗試輸入了一個百分號,發現PreparedStatement竟然沒有轉義,百分號恰好是like查詢的通配符。
     正常情況下,like查詢是這麼寫的:
String sql = "select * from goods where min_name = ?";  // 含有參數
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, "兒童%"); // 參數賦值
System.out.println(st.toString()); 
//com.mysql.jdbc.JDBC4PreparedStatement@8543aa: 
select * from goods where min_name = '兒童%'

  查詢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實現的默認行爲,也就是說,不同的數據庫表現不同,不能一概而論。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章