什么是SQL注入,怎么防止SQL注入?(摘自https://www.cnblogs.com/yaochc/p/4957833.html)
所謂SQL注入,就是通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務器執行惡意的SQL命令。具體來說,它是利用現有應用程序,將(惡意)的SQL命令注入到后台數據庫引擎執行的能力,它可以通過在Web表單中輸入(惡意)SQL語句得到一個存在安全漏洞的網站上的數據庫,而不是按照設計者意圖去執行SQL語句。
怎么防止SQL注入,使用存儲過程來執行所有的查詢;檢查用戶輸入的合法性;將用戶的登錄名、密碼等數據加密保存。
SQL注入攻擊的原理:
假如一個網站的登錄功能的SQL語句字符串是這樣寫的:
"select * from WEBUSERS where ID = '"+ID+"' and PASSWORD = '"+passWord+"'"
如果用戶在ID輸入框中隨便輸入數字,例如:12345678,在密碼輸入框中輸入這樣一串字符:'or 1 = '1
那么這個SQL查詢語句就會變成這樣:
select * from WEBUSERS where ID = '12345678' and PASSWORD = ''or 1 = '1'
無論ID輸入什么,這條SQL語句是永遠都有返回結果的,這樣即使不使用賬號和密碼也可以登錄該網站,甚至可以填入一些惡意的SQL代碼,給網站的數據庫造成威脅。
SQL注入攻擊的解決辦法:
可以使用‘?’占位符創建SQL語句再用預處理PreparedStatement的方法處理SQL語句。
例如:
原DAO的代碼:
1 public int dealLogin_1(String ID, String passWord) {//字符串拼接的方法 2 ConnectOracleDBUtil dbUtil = new ConnectOracleDBUtil(); 3 // 調用方法加載數據庫驅動 4 dbUtil.ConnectOracle(); 5 // 調用方法創建數據庫連接 6 Connection stmt = ConnectOracleDBUtil.getConnection(); 7 // 創建查詢語句 8 String sql0 = "select * from WEBUSERS where ID = '"+ID+"' and PASSWORD = '"+passWord+"'"; 9 Statement stmt0; 10 try { 11 // 發送語句並執行 12 stmt0 = stmt.createStatement(); 13 // 返回結果 14 ResultSet rs0 = stmt0.executeQuery(sql0); 15 //對結果進行處理 16 if(rs0.next()) { 17 System.out.println("賬號和密碼匹配!"); 18 return 1; 19 }else { 20 System.out.println("賬號和密碼不匹配!"); 21 return 2; 22 } 23 } catch (SQLException e) { 24 e.printStackTrace(); 25 }finally { 26 //調用方法關閉與數據庫的連接 27 dbUtil.closed(); 28 } 29 return 0; 30 }
使用預處理PreparedStatement的DAO代碼:
public int dealLogin_2(String ID, String passWord) {//占位符預處理的方法 ConnectOracleDBUtil dbUtil = new ConnectOracleDBUtil(); // 調用方法加載數據庫驅動 dbUtil.ConnectOracle(); // 調用方法創建數據庫連接 Connection stmt = ConnectOracleDBUtil.getConnection(); // 調用方法創建查詢語句 String sql1 = "select * from WEBUSERS where ID = ? and PASSWORD = ?"; // 發送語句並執行 PreparedStatement stmt1 = null; try { stmt1 = stmt.prepareStatement(sql1); stmt1.setString(1, ID); stmt1.setString(2, passWord); // 返回結果 ResultSet rs1 = stmt1.executeQuery(); if(rs1.next()) { System.out.println("賬號和密碼匹配!"); return 1; }else { System.out.println("賬號和密碼不匹配!"); return 2; } } catch (SQLException e) { e.printStackTrace(); }finally { //調用方法關閉與數據庫的連接 dbUtil.closed(); } return 0; }
預處理prepareStatement避免SQL注入漏洞的原理:(摘自https://www.cnblogs.com/yaochc/p/4957833.html)
因為SQL語句在程序運行前已經進行了預編譯,在程序運行時第一次操作數據庫之前,SQL語句已經被數據庫分析,編譯和優化,對應的執行計划也會緩存下來並允許數據庫已參數化的形式進行查詢,當運行時動態地把參數傳給PreprareStatement時,即使參數里有敏感字符如 or '1=1'也數據庫會作為一個參數一個字段的屬性值來處理而不會作為一個SQL指令,如此,就起到了SQL注入的作用了!
先來說說,什么是java中的Statement:Statement是java執行數據庫操作的一個重要方法,用於在已經建立數據庫連接的基礎上,向數據庫發送要執行的SQL語句。具體步驟:
1.首先導入java.sql.*;這個包。
2.然后加載驅動,創建連接,得到Connection接口的的實現對象,比如對象名叫做conn。
3.然后再用conn對象去創建Statement的實例,方法是:Statement stmt = conn.creatStatement("SQL語句字符串");
Statement 對象用於將 SQL 語句發送到數據庫中。實際上有三種 Statement 對象,它們都作為在給定連接上執行 SQL語句的包容器:Statement、PreparedStatement(它從 Statement 繼承而來)和CallableStatement(它從 PreparedStatement 繼承而來)。它們都專用於發送特定類型的 SQL 語句:Statement 對象用於執行不帶參數的簡單 SQL 語句;PreparedStatement 對象用於執行帶或不帶參數的預編譯 SQL 語句;CallableStatement 對象用於執行對數據庫已存儲過程的調用。
綜上所述,總結如下:Statement每次執行sql語句,數據庫都要執行sql語句的編譯,最好用於僅執行一次查詢並返回結果的情形,效率高於PreparedStatement.但存在sql注入風險。PreparedStatement是預編譯執行的。在執行可變參數的一條SQL時,PreparedStatement要比Statement的效率高,因為DBMS預編譯一條SQL當然會比多次編譯一條SQL的效率高。安全性更好,有效防止SQL注入的問題。對於多次重復執行的語句,使用Prepared
Statement效率會更高一點。執行SQL語句是可以帶參數的,並支持批量執行SQL。由於采用了Cache機制,則預編譯的語句,就會放在Cache中,下次執行相同的SQL語句時,則可以直接從Cache中取出來。
1 PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES SET name= ? WHERE ID = ?"); 2 pstmt.setString(1, "李四"); 3 pstmt.setInt(2, 1); 4 pstmt. executeUpdate();
那么CallableStatement擴展了PreparedStatement的接口,用來調用存儲過程,它提供了對於輸入和輸出參數的支持,CallableStatement 接口還有對 PreparedStatement 接口提供的輸入參數的sql查詢的支持。
PreparedStatement: 數據庫會對sql語句進行預編譯,下次執行相同的sql語句時,數據庫端不會再進行預編譯了,而直接用數據庫的緩沖區,提高數據訪問的效率(但盡量采用使用?號的方式傳遞參數),如果sql語句只執行一次,以后不再復用。 從安全性上來看,PreparedStatement是通過?來傳遞參數的,避免了拼sql而出現sql注入的問題,所以安全性較好。
在開發中,推薦使用 PreparedStatement。