1. 程式人生 > 資料庫 >JDBC:使用Statement引發SQL注入

JDBC:使用Statement引發SQL注入

1. 什麼是 SQL 注入?

jdbc程式執行時, sql語句在拼接時由頁面傳入引數,如果使用者惡意傳入一些sql中的特殊關鍵字,會導致sql語句意義發生變化,這種攻擊方式就叫做sql注入。

2. 引子:

sql注入的危害: 黑客可以一行程式碼登入超管賬戶,對資料庫造成不可挽回的損失。


參考使用者註冊登入案例:

// 使用者登入驗證(字串拼接)
String sql = "select * from s_user where loginName = '"+ loginName +"' and loginPwd = '"+ loginPwd +"'";
// 正常使用者:
使用者名稱:admin
密  碼:123456
可以正常登入
 ---------------------------------------------
 // 惡意使用者:
 使用者名稱: aaa   
 密  碼: aaa' or '1'='1  
 也可以登陸成功, 這叫做SQL注入, 

由於 惡意使用者 輸入的密碼被當作sql語句,編譯時,1=1,返回true, 所以驗證通過,登陸成功。


3. 具體例項

下方的登入例項,執行成功後,後臺輸入惡意sql語句,就可以登陸成功。

public class JDBCTest02 {
    public static void main(String[] args) {
        // 初始化介面,(返回使用者名稱/密碼)
        Map<String,String> userLoginInfo = initUi();
        // 驗證使用者名稱和密碼:(傳入登入資訊)
        boolean loginSuccess = login(userLoginInfo);
        // 登陸成功/失敗 ==> 布林值
        // 最後輸出結果
        System.out.println(loginSuccess ? "登陸成功": "登陸失敗");
    }

    /*
    * 使用者登入:
    * @param userLoginInfo 使用者登入資訊
    * @return false 登陸失敗, true 登陸成功
    */
    private static boolean login(Map<String, String> userLoginInfo) {
        // JDBC 程式碼
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        // 單獨定義變數
        String loginName = userLoginInfo.get("loginName");
        String loginPwd  = userLoginInfo.get("loginPwd");
        // 打標記意識
        boolean loginSuccess = false;

        try {
            // 1. 註冊驅動
            Class.forName("com.mysql.jdbc.Driver");
            // 2. 獲取資料庫連線
            // 使用時,把school改為你自己的資料庫名
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root","root");
            // 3. 獲取資料庫操作物件
            stmt = conn.createStatement();
            // 4. 執行SQL
            // 注意: s_user是school裡的使用者表,記得改為你自己定義的的使用者表!!
            String sql = "select * from s_user where loginName = '"+ loginName +"' and loginPwd = '"+ loginPwd +"'";
            rs = stmt.executeQuery(sql);
            // 5. 處理結果集
            if (rs.next()){
                // 登陸成功
                loginSuccess = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            // 6. 釋放資源
            if (rs !=null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt !=null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn !=null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        // return false;
        return loginSuccess;
    }
    /*
    * 初始化使用者介面
    * @return 使用者輸入的使用者名稱和密碼等登入資訊
    *
    */
    private static Map<String, String> initUi() {
        Scanner s = new Scanner(System.in);
        System.out.println("使用者名稱: ");
        String loginName = s.nextLine();
        System.out.println("密碼: ");
        String loginPwd = s.nextLine();
        // 用鍵值對儲存輸入資訊
        Map<String,String> userLoginInfo = new HashMap<>();
        // 傳值
        userLoginInfo.put("loginName", loginName);
        userLoginInfo.put("loginPwd", loginPwd);
        return userLoginInfo;
    }
}

4. 如何防止sql注入?

原理: 讓使用者輸入資訊不參與SQL語句的編譯過程,問題就解決了。
即使使用者提供的資訊中含有SQL語句的關鍵字,但是無法參與編譯,也不起作用。

這裡引入 StatementPreparedStatement

  1. 上面的案例使用了資料庫操作物件 Statement ,讓sql語句直接參與編譯。
  2. SUN公司發明了 PreparedStatement 來讓sql語句預編譯,然後再傳值,間接的阻止了惡意的sql注入。

5. PreperedStatement 相對 Statement 的優點:

  1. 沒有SQL注入的問題。
  2. Statement會使資料庫頻繁編譯SQL,可能造成資料庫緩衝區溢位。
  3. 資料庫和驅動可以對PreperedStatement進行優化(只有在相關聯的資料庫連線沒有關閉的情況下有效)。

6. 總結

SQL注入雖然十幾年前就被淘汰了,但是現在仍然有一些程式設計師偷懶,粗心大意,寫了一些帶有漏洞的程式碼,讓黑客有機可乘,顯得程式猿很不專業,奉勸在座的各位,耗子喂汁。