一句灌、SQL注入
所謂SQL注入,就是通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串欠拾,最終達(dá)到欺騙服務(wù)器執(zhí)行惡意的SQL命令胰锌。
上述語句摘自百度百科,可能對于有些人來說晦澀難懂藐窄,那么我舉個(gè)最簡單的例子匕荸,來體驗(yàn)這一過程;
假設(shè)有一個(gè)登錄表單枷邪,后臺數(shù)據(jù)校驗(yàn)sql為select * from user where username = 'XXX' and password = 'XXX'
,XXX為傳入的用戶名和密碼诺凡,根據(jù)sql返回的結(jié)果判斷登錄是否成功东揣;(方便解釋,簡單處理)
這條sql是預(yù)先拼接好然后提交給數(shù)據(jù)庫執(zhí)行的腹泌,假設(shè)我們把password
的內(nèi)容改為1' or '1' = '1
嘶卧,注意里面的單引號!如果登錄名為caojiantao
凉袱,那么最終生成的sql便是:
select * from user where username = 'caojiantao' and password = '1' or '1' = '1'
=嬉鳌!這樣一來不用知道用戶caojiantao
的密碼也能夠登錄成功了专甩,更有甚者钟鸵,在密碼處輸入"1';drop table user;"
,直接刪除了數(shù)據(jù)表涤躲,十分的危險(xiǎn)棺耍。
這就是一個(gè)最簡單sql注入的例子,輸入包含sql命令的內(nèi)容种樱,欺騙服務(wù)器執(zhí)行破壞數(shù)據(jù)蒙袍。
二俊卤、Statement
JDBC核心接口,對象用于將SQL語句發(fā)送到數(shù)據(jù)庫中害幅。一個(gè)簡單的demo演示Statement的使用消恍;
public static void main(String[] args) {
// 數(shù)據(jù)庫配置
String url = "jdbc:mysql://127.0.0.1/ssm";
String name = "com.mysql.jdbc.Driver";
String user = "root";
String password = "Cjt00382114.";
// 登錄賬號密碼
String username = "caojiantao";
String pwd = "123";
// 拼接sql
String sql = "select * from user where username = '" + username + "' and password = '" + pwd + "'";
try {
// jdbc操作
Class.forName(name);
Connection connection = DriverManager.getConnection(url, user, password);
// Statement
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
System.out.println(resultSet.getString("nickname"));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
代碼很簡單,判斷用戶名和密碼是否匹配以现,匹配則輸出用戶的nickname
昵稱狠怨;
根據(jù)上面sql注入的問題,我們試著把pwd傳入1' or '1' = '1
叼风,前面的1可以隨便填取董,運(yùn)行程序可以發(fā)現(xiàn)輸出了用戶昵稱曹建濤
,說明sql注入成功无宿,那么茵汰,我們該怎么避免呢?
三孽鸡、PreparedStatement
采用字符串匹配蹂午?篩選sql命令字符串?沒那么麻煩彬碱,JDBC已經(jīng)有現(xiàn)成的處理方案了豆胸,那就是PreparedStatement
;
PreparedStatement
繼承自Statement
巷疼,字面可譯為預(yù)聲明晚胡,強(qiáng)調(diào)一個(gè)預(yù),內(nèi)部包含一個(gè)預(yù)編譯的sql語句嚼沿,參數(shù)采用占位符?
進(jìn)行填充估盘,還是看一段代碼體會下;
public static void main(String[] args) {
// 數(shù)據(jù)庫配置
String url = "jdbc:mysql://127.0.0.1/ssm";
String name = "com.mysql.jdbc.Driver";
String user = "root";
String password = "Cjt00382114.";
// 登錄賬號密碼
String username = "caojiantao";
String pwd = "123";
String preSql = "select * from user where username = ? and password = ?";
try {
// jdbc操作
Class.forName(name);
Connection connection = DriverManager.getConnection(url, user, password);
// PreparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(preSql);
preparedStatement.setString(1, username);
preparedStatement.setString(2, pwd);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString("nickname"));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
我們可以修改pwd內(nèi)容為1' or '1' = '1
骡尽,運(yùn)行程序沒有任何輸出遣妥,說明PreparedStatement
有效地避免了sql注入問題;
因?yàn)镾QL語句在程序運(yùn)行前已經(jīng)進(jìn)行了預(yù)編譯攀细,在程序運(yùn)行時(shí)第一次操作數(shù)據(jù)庫之前箫踩,SQL語句已經(jīng)被數(shù)據(jù)庫分析,編譯和優(yōu)化谭贪,對應(yīng)的執(zhí)行計(jì)劃也會緩存下來并允許數(shù)據(jù)庫已參數(shù)化的形式進(jìn)行查詢境钟,當(dāng)運(yùn)行時(shí)動(dòng)態(tài)地把參數(shù)傳給PreprareStatement時(shí),即使參數(shù)里有敏感字符如 or '1=1'也數(shù)據(jù)庫會作為一個(gè)參數(shù)一個(gè)字段的屬性值來處理而不會作為一個(gè)SQL指令俭识,如此吱韭,就起到了SQL注入的作用了!
上述摘自java中預(yù)處理PrepareStatement為什么能起到防止SQL注入的作用?理盆?6幻骸!
因?yàn)槭抢^承關(guān)系猿规,因而Statement
具備的PreparedStatement
全都有衷快,而且PreparedStatement
還具備獨(dú)有的預(yù)處理功能,相比Statement
姨俩,PreparedStatement
好處有三:
- 提高代碼的可讀性蘸拔,便于維護(hù);
- 提高了sql執(zhí)行效率环葵;
- 增強(qiáng)了安全性调窍,避免sql注入;
四张遭、花絮——myBatis的 # 和 $
# 相當(dāng)于對數(shù)據(jù) 加上 雙引號邓萨,$ 相當(dāng)于直接顯示數(shù)據(jù)。
一句話言簡意賅菊卷。分條陳述:
- # 能夠sql注入問題缔恳,$ 不行;
- $ 用于傳入數(shù)據(jù)庫對象洁闰,例如表名歉甚;
- 能用 # 就不要使用 $;
參考文章:mybatis中的#和$的區(qū)別