聊聊SQL隱碼攻擊

2022-09-30 21:00:23

SQL隱碼攻擊問題

  • 概述:

    • 首先SQL隱碼攻擊是一個非常危險的操作,很可能被一些不懷好意的人鑽空導致我們系統出現異常等狀況,比如資料庫遭到破壞或被入侵。
  • 直接原因:

    • 在頁面中有資料互動的地方,攻擊者構造sql語句,使web伺服器執行惡意命令存取資料庫。
  • 根本原因:伺服器端沒有嚴格檢驗使用者資料導致SQL隱碼攻擊漏洞,像使用JDBC的Statement語句新增SQL語句,如下:

    • 由於我們的JDBC在對資料庫進行操作時,需要使用者端傳入一些引數。我們在日常中的處理是將字串引數作為SQL語句進行拼接,但是加入使用者端傳入SQL語句關鍵字惡意篡改SQL語句就會改變伺服器端SQL語意發生系統異常。嚴重時就會導致系統和資料庫破壞,這時的攻擊方式就叫SQL隱碼攻擊了。

    • 範例:模擬登入請求傳入使用者id和密碼引數,使用字串拼接導致的SQL隱碼攻擊。

      • 拼接SQL語句,就會出現SQL隱碼攻擊的安全問題,拼接程式碼如下:

        String sql = "select * from user where username='" + uid + "' and password='" + passwd + "'";
        
      • 若此時傳入引數如下:永真式萬能密碼 或 封號結束註釋後面條件驗證(只能說人的腦洞真大哈哈),還有更奇葩的像 Union 注入

        params.put("uid", "malongfei");
        params.put("passwd", "111' or '1' = '1"); 
        // 或者
        params.put("uid", "malongfei'; -- ")
        // 或者
        params.put("uid", "malongfei'; # ")
        
      • 此時JDBC還沒意識到安全問題,依舊將以上引數拼接到我們的SQL原語中,如下:

        select * from user where uid = 'malongfei' and passwd = '111' or '1' = '1';
        select * from user where uid = 'malongfei'; -- ' and passwd = '111' or '1' = '1';
        select * from user where uid = 'malongfei'; # ' and passwd = '111' or '1' = '1';
        
  • 預防SQL隱碼攻擊:使用PreparedStatement代替Statement可以有效防止SQL隱碼攻擊。

    • PreparedStatement利用預編譯的機制將sql語句的主幹和引數分別傳輸給資料庫伺服器,這樣即使引數中攜帶資料庫關鍵字,也不能作為SQL中真正的關鍵字而起作用。
    // 後端登入驗證密碼介面的SQL語句
    select * from user where uid = ? and passwd = ?;
    
    • 設定黑名單也可提前預防,單純針對於使用者輸入中含有SQL關鍵字的攔截方法,比如在註冊賬號時,使用者名稱和密碼中不能含有SQL語句關鍵字;
    • 或者說在進行SQL拼接時加入邏輯處理,對傳入引數含有SQL關鍵字的進行報輸入異常。
  • PreparedStatementStatment 區別:

    1. 語法不同:PreparedStatement 使用預編譯的sql,而 Statment 使用靜態的sql
    2. 效率不同: PreparedStatement 具有 sql快取區,效率比 Statment 高
    3. 安全性不同:PreparedStatement 可以有效防止sql注入,而 Statment 不能

Mybatis對SQL隱碼攻擊的預防處理

  • 出現SQL隱碼攻擊問題的原因和上面一樣,都是由於拼接SQL導致的,只不過方式不同。

    • Mybatis接收引數處理有兩種語法:#{}${}#使用預編譯,$使用拼接SQL方式。
    • 這裡需要注意的是:使用#運運算元,Mybatis會將傳入的引數當成一個字串,在進行變數替換時會加上引號!
  • mybatis 出現SQL隱碼攻擊範例:

    • 模糊查詢時,如下範例:

      • 採用 #{} 的話程式會報異常。最後替換成 like "'name'"

        select * from users where name like '%#{name}%'
        
      • 常人看了既然#{}報錯那麼我用${},正中SQL隱碼攻擊的下懷,這個時候倘若我們的伺服器端 Java 程式碼沒有對傳入引數進行攔截處理,SQL隱碼攻擊條件滿足!

        select * from users where name like '%${name}%'
        
      • 正確SQL寫法,需要使用 concat函數 來進行連線引數(concat為mysql函數,連線引數產生字串)

        select * from users where name like concat('%',#{name}, '%')
        
  • 補充:

    • in 之後的多個引數在mybatis中也不能採用 #{} 或者 ${} ,需要使用動態SQL語法中 foreach 迴圈遍歷

      select * from users where id in
      <foreach collection="ids" item="item" open="("separatosr="," close=")">
      	#{item}
      </foreach>
      
    • order by 之後也不能使用 #{},他也會將欄位改為字串形式,加上引號後就不能正常排序,所以我們需要考慮 ${} 的方式,但是在後臺程式碼中一定要進行資料引數的校驗等手段,防止SQL隱碼攻擊.