SQL注入是一種很簡單的攻擊手段,但直到今天仍然十分常見。究其緣由不外乎:No patch for stupid。前端
爲何這麼說,下面就以JAVA爲例進行說明:java
假設數據庫中存在這樣的表:sql
table user( id varchar(20) PRIMARY KEY ,數據庫
name varchar(20) ,編程
age varchar(20) );mybatis
而後使用JDBC操做表:app
private String getNameByUserId(String userId) {框架
Connection conn = getConn;//得到鏈接開發
String sql = "select name from user where id=" + userId;字符串
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs=pstmt.executeUpdate; ......
}
上面的代碼常常被一些開發人員使用。想象這樣的狀況,當傳入的userId參數爲"3;drop table user;"時,執行的sql語句以下:
select name from user where id=3; drop table user;
數據庫在編譯執行以後,刪除了user表。瞧,一個簡單的SQL注入攻擊生效了!之因此這樣,是由於上面的代碼沒有符合編程規範。 當咱們按照規範編程時,SQL注入就不存在了。
這也是避免SQL注入的第一種方式:
預編譯語句,代碼以下:
Connection conn = getConn;//得到鏈接
String sql = "select name from user where id= ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userId);
ResultSet rs=pstmt.executeUpdate; ......
爲何上面的代碼就不存在SQL注入了呢?由於使用了預編譯語句,預編譯語句在執行時會把"select name from user where id= ?"語句事先編譯好,這樣當執行時僅僅須要用傳入的參數替換掉?佔位符便可。
而對於第一種不符合規範的狀況,程序會先生成sql語句,而後帶着用戶傳入的內容去編譯,這偏偏是問題所在。 除了使用預編譯語句以外,還有第二種避免SQL注入攻擊的方式:存儲過程。
存儲過程(Stored Procedure)是一組完成特定功能的SQL語句集,經編譯後存儲在數據庫中,用戶經過調用存儲過程並給定參數(若是該存儲過程帶有參數)就能夠執行它,也能夠避免SQL注入攻擊
Connection conn = getConn;
stmt = conn.prepareCall("{call name_from_user(?,?)}");
stmt.setInt(1,2);
stmt.registerOutParameter(2, Types.VARCHAR);
stmt.execute;
String name= stmt.getString(2);
上面的代碼中對應的存儲過程以下:
use user; delimiter // create procedure name_from_user(in user_id int,out user_name varchar(20)) begin select name into user_name from user where id=user_id; end // delimiter ; 固然用戶也能夠在前端作字符檢查,這也是一種避免SQL注入的方式:好比對於上面的userId參數,用戶檢查到包含分號就提示錯誤。
不過,從最根本的緣由看,SQL注入攻擊之因此存在,是由於app在訪問數據庫時沒有使用最小權限。想來也是,你們好像一直都在使用root帳號訪問數據庫。 那麼mybatis是如何避免sql注入攻擊的呢?仍是以上面的表user爲例: 假設mapper文件爲:
SELECT name FROM user where id = #{userId} 對應的java文件爲:
public interface UserMapper{ String getNameByUserId(@Param("userId") String userId); }
能夠看到輸入的參數是String類型的userId,當咱們傳入userId="34;drop table user;"後,打印的語句是這樣的:
select name from user where id = ? 無論輸入何種userID,他的sql語句都是這樣的。
這就得益於mybatis在底層實現時使用預編譯語句。數據庫在執行該語句時,直接使用預編譯的語句,而後用傳入的userId替換佔位符?就去運行了。不存在先替換佔位符?再進行編譯的過程,所以SQL注入也就沒有了生存的餘地了。
那麼mybatis是如何作到sql預編譯的呢?其實框架底層使用的正是PreparedStatement類。PreparedStaement類不但可以避免SQL注入,由於已經預編譯,當N次執行同一條sql語句時,節約了(N-1)次的編譯時間,從而可以提升效率。
若是將上面的語句改爲: SELECT name FROM user where id = ${userId} 當咱們輸入userId="34;drop table user;"後,打印的語句是這樣的: select name from user where id = 34;drop table user; 此時,mybatis沒有使用預編譯語句,它會先進行字符串拼接再執行編譯,這個過程正是SQL注入生效的過程。
所以在編寫mybatis的映射語句時,儘可能採用「#{xxx}」這樣的格式。若不得不使用「${xxx}」這樣的參數,要手工地作好過濾工做,來防止sql注入攻擊。