如何保護JDBC應用程序免受SQL注入

總覽

在關係數據庫管理系統(RDBMS)中,有一種特定的語言(稱爲SQL(結構化查詢語言))用於與數據庫進行通信。用SQL編寫的查詢語句用於操縱數據庫的內容和結構。創建和修改數據庫結構的特定SQL語句稱爲DDL(數據定義語言)語句,而操作數據庫內容的語句稱爲DML(數據操作語言)語句。與RDBMS包關聯的引擎解析並解釋SQL句,並相應地返回結果。這是與RDBMS進行通信的典型過程,僅執行一條SQL語句並取回結果即可。系統不會判斷任何符合該語言的語法和語義結構的語句的意圖。這也意味着沒有驗證或驗證過程可以檢查誰觸發了該語句以及獲得輸出的特權。***者可以簡單地以惡意意圖觸發SQL語句,並獲取不應獲取的信息。例如,***者可以使用具有無害外觀的惡意負載執行SQL語句,並使用無害查詢來控制Web應用程序的數據庫服務器。抽絲剝繭 細說架構那些事——【優銳課】

怎麼運行的?

***者可以利用此漏洞,並利用其自身的優勢。例如,可以繞過應用程序的身份驗證和授權機制,並從整個數據庫中檢索所謂的安全內容。SQL注入可用於創建,更新和刪除數據庫中的記錄。因此,可以使用SQL來制定一個僅限於自己的想象力的查詢。

通常,應用程序經常出於多種目的向數據庫觸發SQL查詢,例如,用於獲取某些記錄,創建報告,驗證用戶身份,CRUD事務等等。***者只需要在某些應用程序輸入表單中查找SQL輸入查詢即可。然後,可以使用表單準備的查詢來纏繞惡意內容,以便在應用程序觸發查詢時,它也攜帶注入的有效負載。

理想情況之一是當應用程序要求用戶輸入諸如用戶名或用戶ID之類的內容時。該應用程序在那裏打開了一個薄弱環節。SQL語句可以在不知不覺中運行。***者通過注入有效載荷來利用該有效載荷,該有效載荷將用作SQL查詢的一部分並由數據庫進行處理。例如,用於登錄表單的POST操作的服務器端僞代碼可能是:

uname = getRequestString("username");
pass = getRequestString("passwd");
 
stmtSQL = "SELECT * FROM users WHERE
   user_name = '" + uname + "' AND passwd = '" + pass + "'";
 
database.execute(stmtSQL);


前面的代碼很容易受到SQL注入***的***,因爲通過變量'uname''pass'SQL語句提供的輸入可以通過改變語句語義的方式進行操作。

例如,我們可以像在MySQL中一樣修改查詢以使其針對數據庫服務器運行。

stmtSQL = "SELECT * FROM users WHERE
   user_name = '" + uname + "' AND passwd = '" + pass + "' OR 1=1";


這導致將原始SQL語句修改爲某種程度,以使其可以繞過身份驗證。這是一個嚴重的漏洞,必須在代碼中加以防止。

防範SQL注入***

減少SQL注入***機會的方法之一是確保在執行之前,不允許將未經過濾的文本字符串附加到SQL語句。例如,我們可以使用PreparedStatement執行所需的數據庫任務。

PreparedStatement有趣的方面是,它將預編譯的SQL語句發送到數據庫,而不是字符串。這意味着查詢和數據分別發送到數據庫。這防止了SQL注入***的根本原因,因爲在SQL注入中,這種想法是將代碼和數據混合在一起,其中數據實際上是以數據爲幌子的一部分。在PreparedStatement中,有多個setXYZ()方法,例如setString()。這些方法用於過濾特殊字符,例如SQL語句中包含的引號。

例如,我們可以通過以下方式執行SQL語句。

String sql = "SELECT * FROM employees WHERE emp_no = "+eno;


不用在輸入中輸入eno = 10125作爲員工編號,我們可以使用輸入修改查詢,例如:

eno = 10125 OR 1=1


這完全改變了查詢返回的結果。

舉例

在以下示例代碼中,我們展示了PreparedStatement如何可用於執行數據庫任務。

package org.mano.example;
 
import java.sql.*;
import java.time.LocalDate;
public class App
{
   static final String JDBC_DRIVER =
      "com.mysql.cj.jdbc.Driver";
   static final String DB_URL =
      "jdbc:mysql://localhost:3306/employees";
   static final String USER = "root";
   static final String PASS = "secret";
   public static void main( String[] args )
   {
      String selectQuery = "SELECT * FROM employees
         WHERE emp_no = ?";
      String insertQuery = "INSERT INTO employees
         VALUES (?,?,?,?,?,?)";
      String deleteQuery = "DELETE FROM employees
         WHERE emp_no = ?";
      Connection connection = null;
      try {
         Class.forName(JDBC_DRIVER);
         connection = DriverManager.getConnection
            (DB_URL, USER, PASS);
      }catch(Exception ex) {
         ex.printStackTrace();
      }
      try(PreparedStatement pstmt =
            connection.prepareStatement(insertQuery);){
         pstmt.setInt(1,99);
         pstmt.setDate(2, Date.valueOf
            (LocalDate.of(1975,12,11)));
         pstmt.setString(3,"ABC");
         pstmt.setString(4,"XYZ");
         pstmt.setString(5,"M");
         pstmt.setDate(6,Date.valueOf(LocalDate.of(2011,1,1)));
         pstmt.executeUpdate();
         System.out.println("Record inserted successfully.");
      }catch(SQLException ex){
         ex.printStackTrace();
      }
      try(PreparedStatement pstmt =
            connection.prepareStatement(selectQuery);){
         pstmt.setInt(1,99);
         ResultSet rs = pstmt.executeQuery();
         while(rs.next()){
            System.out.println(rs.getString(3)+
               " "+rs.getString(4));
         }
      }catch(Exception ex){
         ex.printStackTrace();
      }
      try(PreparedStatement pstmt =
            connection.prepareStatement(deleteQuery);){
         pstmt.setInt(1,99);
         pstmt.executeUpdate();
         System.out.println("Record deleted
            successfully.");
      }catch(SQLException ex){
         ex.printStackTrace();
      }
      try{
         connection.close();
      }catch(Exception ex){
         ex.printStackTrace();
      }
   }
}


A Glimpse into PreparedStatement

這些作業也可以通過JDBC語句接口來完成,但是問題在於它有時可能會非常不安全,尤其是當執行動態SQL語句來查詢將用戶輸入值與SQL查詢連接在一起的數據庫時。如我們所見,這可能是危險的情況。在大多數情況下,Statement是相當無害的,但PreparedStatement似乎是兩者之間更好的選擇,由於防止將惡意字符串發送到數據庫的方式不同,因此它可以防止串聯惡意字符串。PreparedStatement使用變量替換而不是串聯。在SQL查詢中放置問號(?)表示替換變量將代替其位置並在執行查詢時提供值。替換變量的位置根據setXYZ()方法中分配的參數索引位置來代替。

此技術可以防止它受到SQL注入***。

此外,PreparedStatement實現了AutoCloseable。這使它可以在try-with-resources塊的上下文中進行寫入,並在超出範圍時自動關閉。

結論

只有負責任地編寫代碼,才能防止SQL注入***。實際上,在任何軟件解決方案中,大多數安全性都是由於不良的編碼做法而被破壞的。在這裏,我們描述了應避免的事項以及PreparedStatement如何幫助我們編寫安全代碼。有關SQL注入的完整想法,請參考適當的材料;有關SQL注入的詳細信息,請參見。Internet上充滿了它們,對於PreparedStatement,請查閱Java API文檔以獲取更詳細的說明。

歡迎留言或私信探討學習~

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章