sql注入是一種很簡單的攻擊手段,但直到今天仍然十分常見。究其原因不外乎:no patch for stupid。為什么這么說,下面就以java為例進行說明:
假設數據庫中存在這樣的表:
1
2
3
4
|
table user( id varchar( 20 ) primary key , name varchar( 20 ) , age varchar( 20 ) ); |
然后使用jdbc操作表:
1
2
3
4
5
6
7
|
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語句如下:
1
|
select name from user where id= 3 ; drop table user; |
數據庫在編譯執行之后,刪除了user表。瞧,一個簡單的sql注入攻擊生效了!之所以這樣,是因為上面的代碼沒有符合編程規范。
當我們按照規范編程時,sql注入就不存在了。這也是避免sql注入的第一種方式:預編譯語句,代碼如下:
1
2
3
4
5
6
|
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注入攻擊
1
2
3
4
5
6
|
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 ); |
上面的代碼中對應的存儲過程如下:
1
2
3
4
5
6
7
8
|
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文件為:
1
2
3
|
<select id= "getnamebyuserid" resulttype= "string" > select name from user where id = #{userid} </select> |
對應的java文件為:
1
2
3
|
public interface usermapper{ string getnamebyuserid( @param ( "userid" ) string userid); } |
可以看到輸入的參數是string類型的userid,當我們傳入userid="34;drop table user;"后,打印的語句是這樣的:
1
|
select name from user where id = ? |
不管輸入何種userid,他的sql語句都是這樣的。這就得益于mybatis在底層實現時使用預編譯語句。數據庫在執行該語句時,直接使用預編譯的語句,然后用傳入的userid替換占位符?就去運行了。不存在先替換占位符?再進行編譯的過程,因此sql注入也就沒有了生存的余地了。
那么mybatis是如何做到sql預編譯的呢?其實框架底層使用的正是preparedstatement類。preparedstaement類不但能夠避免sql注入,因為已經預編譯,當n次執行同一條sql語句時,節約了(n-1)次的編譯時間,從而能夠提高效率。
如果將上面的語句改成:
1
2
3
|
<select id= "getnamebyuserid" resulttype= "string" > select name from user where id = ${userid} </select> |
當我們輸入userid="34;drop table user;"
后,打印的語句是這樣的:
1
|
select name from user where id = 34 ;drop table user; |
此時,mybatis沒有使用預編譯語句,它會先進行字符串拼接再執行編譯,這個過程正是sql注入生效的過程。
因此在編寫mybatis的映射語句時,盡量采用“#{xxx}”這樣的格式。若不得不使用“${xxx}”這樣的參數,要手工地做好過濾工作,來防止sql注入攻擊。
總結
以上所述是小編給大家介紹的mybatis防止sql注入的方法實例詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:https://blog.csdn.net/bwh0520/article/details/80102040