這篇文章主要介紹mybatis如何防止SQL注入,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
成都創(chuàng)新互聯(lián)自2013年創(chuàng)立以來(lái),先為瓊海等服務(wù)建站,瓊海等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為瓊海企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。
SQL注入是一種很簡(jiǎn)單的攻擊手段,但直到今天仍然十分常見(jiàn)。究其原因不外乎:No patch for stupid。為什么這么說(shuō),下面就以JAVA為例進(jìn)行說(shuō)明:
假設(shè)數(shù)據(jù)庫(kù)中存在這樣的表:
table user( id varchar(20) PRIMARY KEY , name varchar(20) , age varchar(20) );
然后使用JDBC操作表:
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(); ...... }
上面的代碼經(jīng)常被一些開發(fā)人員使用。想象這樣的情況,當(dāng)傳入的userId參數(shù)為"3;drop table user;"時(shí),執(zhí)行的sql語(yǔ)句如下:
select name from user where id=3; drop table user;
數(shù)據(jù)庫(kù)在編譯執(zhí)行之后,刪除了user表。瞧,一個(gè)簡(jiǎn)單的SQL注入攻擊生效了!之所以這樣,是因?yàn)樯厦娴拇a沒(méi)有符合編程規(guī)范。
當(dāng)我們按照規(guī)范編程時(shí),SQL注入就不存在了。這也是避免SQL注入的第一種方式:預(yù)編譯語(yǔ)句,代碼如下:
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注入了呢?因?yàn)槭褂昧祟A(yù)編譯語(yǔ)句,預(yù)編譯語(yǔ)句在執(zhí)行時(shí)會(huì)把"select name from user where id= ?"語(yǔ)句事先編譯好,這樣當(dāng)執(zhí)行時(shí)僅僅需要用傳入的參數(shù)替換掉?占位符即可。而對(duì)于第一種不符合規(guī)范的情況,程序會(huì)先生成sql語(yǔ)句,然后帶著用戶傳入的內(nèi)容去編譯,這恰恰是問(wèn)題所在。
除了使用預(yù)編譯語(yǔ)句之外,還有第二種避免SQL注入攻擊的方式:存儲(chǔ)過(guò)程。存儲(chǔ)過(guò)程(Stored Procedure)是一組完成特定功能的SQL語(yǔ)句集,經(jīng)編譯后存儲(chǔ)在數(shù)據(jù)庫(kù)中,用戶通過(guò)調(diào)用存儲(chǔ)過(guò)程并給定參數(shù)(如果該存儲(chǔ)過(guò)程帶有參數(shù))就可以執(zhí)行它,也可以避免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);
上面的代碼中對(duì)應(yīng)的存儲(chǔ)過(guò)程如下:
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 ;
當(dāng)然用戶也可以在前端做字符檢查,這也是一種避免SQL注入的方式:比如對(duì)于上面的userId參數(shù),用戶檢查到包含分號(hào)就提示錯(cuò)誤。
不過(guò),從最根本的原因看,SQL注入攻擊之所以存在,是因?yàn)閍pp在訪問(wèn)數(shù)據(jù)庫(kù)時(shí)沒(méi)有使用最小權(quán)限。想來(lái)也是,大家好像一直都在使用root賬號(hào)訪問(wèn)數(shù)據(jù)庫(kù)。
那么mybatis是如何避免sql注入攻擊的呢?還是以上面的表user為例:
假設(shè)mapper文件為:
對(duì)應(yīng)的java文件為:
public interface UserMapper{ String getNameByUserId(@Param("userId") String userId); }
可以看到輸入的參數(shù)是String類型的userId,當(dāng)我們傳入userId="34;drop table user;"后,打印的語(yǔ)句是這樣的:
select name from user where id = ?
不管輸入何種userID,他的sql語(yǔ)句都是這樣的。這就得益于mybatis在底層實(shí)現(xiàn)時(shí)使用預(yù)編譯語(yǔ)句。數(shù)據(jù)庫(kù)在執(zhí)行該語(yǔ)句時(shí),直接使用預(yù)編譯的語(yǔ)句,然后用傳入的userId替換占位符?就去運(yùn)行了。不存在先替換占位符?再進(jìn)行編譯的過(guò)程,因此SQL注入也就沒(méi)有了生存的余地了。
那么mybatis是如何做到sql預(yù)編譯的呢?其實(shí)框架底層使用的正是PreparedStatement類。PreparedStaement類不但能夠避免SQL注入,因?yàn)橐呀?jīng)預(yù)編譯,當(dāng)N次執(zhí)行同一條sql語(yǔ)句時(shí),節(jié)約了(N-1)次的編譯時(shí)間,從而能夠提高效率。
如果將上面的語(yǔ)句改成:
當(dāng)我們輸入userId="34;drop table user;"
后,打印的語(yǔ)句是這樣的:
select name from user where id = 34;drop table user;
此時(shí),mybatis沒(méi)有使用預(yù)編譯語(yǔ)句,它會(huì)先進(jìn)行字符串拼接再執(zhí)行編譯,這個(gè)過(guò)程正是SQL注入生效的過(guò)程。
因此在編寫mybatis的映射語(yǔ)句時(shí),盡量采用“#{xxx}”這樣的格式。若不得不使用“${xxx}”這樣的參數(shù),要手工地做好過(guò)濾工作,來(lái)防止sql注入攻擊。
以上是“mybatis如何防止SQL注入”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!