Mybatis如何防止SQL注入
什么是SQL注入
sql注入是一種代碼注入技術,將惡意的sql插入到被執(zhí)行的字段中,以不正當?shù)氖侄味鄶?shù)據(jù)庫信息進行操作。
在項目開發(fā)當中使用的用戶一般都是以root用戶進行使用而沒有
例如
一條普通的查詢SQL
select name from user where id = 1;
SQL注入的SQL
select name from user where id = 1;drop table user;
MySQL防止SQL注入
MySQL中的實現(xiàn)方案
預編譯 - 使用占位符
Connection conn = getConn();//獲得連接
String sql = "select name from user where id= ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userId);
ResultSet rs=pstmt.executeUpdate();
......
? 代表的就是占位符
實現(xiàn)原理:
為什么上面的代碼就不存在SQL注入了呢?因為使用了預編譯語句,預編譯語句在執(zhí)行時會把"select name from user where id= ?"語句事先編譯好,這樣當執(zhí)行時僅僅需要用傳入的參數(shù)替換掉占位符即可。<u>也就是不會傳入drop table user</u> ,只保證傳入?yún)?shù)而不傳入SQL。
存儲過程
存儲過程(Stored Procedure)是一組完成特定功能的SQL語句集,經編譯后存儲在數(shù)據(jù)庫中,用戶通過調用存儲過程并給定參數(shù)(如果該存儲過程帶有參數(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);
存儲過程的實現(xiàn)
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 ;
字符處理
使用后端做字符處理(PS:如果是使用前端做處理的話,黑客可以越過前端傳入?yún)?shù));
利用字符串處理傳入的參數(shù)
權限設置
上述的三種處理辦法實質上都是對參數(shù)進行處理,權限設置的話就是對用戶設置最小權限粒度。
創(chuàng)建用戶 - create user
-
修改用戶權限 - GRANT privileges
常用的權限
- 表數(shù)據(jù): select, update, delete, insert
- 表結構: create, alert, drop
- 外鍵: references
- 創(chuàng)建臨時表: create temporary tables
- 操作索引: index
- 視圖: create view, show view
- 存儲過程: create routine, alert routine, execute
- 所有權限: all
在連接數(shù)據(jù)庫的時候不使用root用戶,而是創(chuàng)建其他的用戶,根據(jù)業(yè)務來賦予不同的權限,但是對于DDL不對其他用戶進行賦予操作。
MyBatis如何實現(xiàn)的SQL注入
<select id="getNameByUserId" resultType="String">
SELECT name FROM user where id = #{userId}
</select>
對應的java文件為:
public interface UserMapper{
String getNameByUserId(@Param("userId") String userId);
}
可以看到輸入的參數(shù)是String類型的userId,當我們傳入userId="34;drop table user;"后,打印的語句(Mybatis日志)是這樣的:
select name from user where id = ?
不管輸入何種userID,他的sql語句都是這樣的。這就得益于mybatis在底層實現(xiàn)時使用預編譯語句。數(shù)據(jù)庫在執(zhí)行該語句時,直接使用預編譯的語句,然后用傳入的userId替換占位符?就去運行了。不存在先替換占位符?再進行編譯的過程,因此SQL注入也就沒有了生存的余地了。
那么mybatis是如何做到sql預編譯的呢?其實框架底層使用的正是PreparedStatement類。PreparedStaement類不但能夠避免SQL注入,因為已經預編譯,當N次執(zhí)行同一條sql語句時,節(jié)約了(N-1)次的編譯時間,從而能夠提高效率。
如果將上面的語句改成:
<select id="getNameByUserId" resultType="String">
SELECT name FROM user where id = ${userId}
</select>
當我們輸入userId="34;drop table user;"后,打印的語句是這樣的:
select name from user where id = 34;drop table user;
此時,mybatis沒有使用預編譯語句,它會先進行字符串拼接再執(zhí)行編譯,這個過程正是SQL注入生效的過程。
因此在編寫mybatis的映射語句時,盡量采用“#{xxx}”這樣的格式。若不得不使用“${xxx}”這樣的參數(shù),要手工地做好過濾工作,來防止sql注入攻擊。