前沿
在現(xiàn)有的框架中sql防注入已經(jīng)做得很好了禽最,我們需要做的就是盡量不要使用sql拼接調(diào)用
java sql注入原因以及預(yù)防方案(易理解)
1. SQL注入
1.1 原理
SQL注入是通過客戶端的輸入把SQL命令注入到一個(gè)應(yīng)用的數(shù)據(jù)庫中虑乖,從而執(zhí)行惡意的SQL語句。
1.2 演示
1.2.1 案例1
有一個(gè)登錄框洪灯,需要輸入用戶名和密碼,然后我們的密碼輸入 'or '123' = '123 這樣的。我們在查詢用戶名和密碼是否正確的時(shí)候祥得,本來執(zhí)行的sql語句是:select * from user where username = '' and password = ''. 這樣的sql語句创千,現(xiàn)在我們輸入密碼是如上這樣的殿雪,然后我們會(huì)通過參數(shù)進(jìn)行拼接,拼接后的sql語句就是:
select * from user where username = '' and password = ' ' or '123' = '123 '; 這樣的了其骄,那么會(huì)有一個(gè)or語句亏镰,只要這兩個(gè)有一個(gè)是正確的話逼肯,就條件成立,因此 123 = 123 是成立的篮幢。因此驗(yàn)證就會(huì)被跳過奈揍。這只是一個(gè)簡單的列子昆箕,
1.2.2 案例2
密碼比如是這樣的:'; drop table user;, 這樣的話,那么sql命令就變成了:
select * from user where username = '' and password = ''; drop table user;' , 那么這個(gè)時(shí)候我們會(huì)把user表直接刪除了带射。
1.3 防范
1.3.1 前端
- 前端表單進(jìn)行參數(shù)格式控制;
1.3.2 后端
我們可以使用預(yù)編譯語句(PreparedStatement跪楞,這 樣的話即使我們使用sql語句偽造成參數(shù)缀去,到了服務(wù)端的時(shí)候,這個(gè)偽造sql語句的參數(shù)也只是簡單的字符甸祭,并不能起到攻擊的作用缕碎。
使用正則表達(dá)式過濾傳入的參數(shù)
注意: 永遠(yuǎn)也不要把未經(jīng)檢查的用戶輸入的值直接傳給數(shù)據(jù)庫
- java中的驗(yàn)證字符串是否包含sql的判斷
package cn.javanode.thread;
import java.util.regex.Pattern;
/**
* @author xgt(小光頭)
* @version 1.0
* @date 2021-1-8 11:48
*/
public class CheckSqlDemo {
/**正則表達(dá)式**/
private static String reg = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|"
+ "(\\b(select|update|union|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)";
private static Pattern sqlPattern = Pattern.compile(reg, Pattern.CASE_INSENSITIVE);
private static boolean isValid(String str) {
if (sqlPattern.matcher(str).find())
{
System.out.println("未能通過過濾器:str=" + str);
return false;
}
return true;
}
public static void main(String[] args) {
System.out.println(isValid("tongji_user_add"));
}
}
補(bǔ)充
PreparedStatement是如何防止SQL注入的?
1. 拼接參數(shù)(sql注入)
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement preparedStatement = connection.prepareStatement(sql);
String param = "'test' or 1=1";
String sql = "select file from file where name = " + param; // 拼接SQL參數(shù)
ResultSet resultSet = preparedStatement.executeQuery();
System.out.println(resultSet.next());
輸出結(jié)果為true
池户,DB中執(zhí)行的SQL為
-- 永真條件1=1成為了查詢條件的一部分咏雌,可以返回所有數(shù)據(jù)凡怎,造成了SQL注入問題
select file from file where name = 'test' or 1=1
2. setString (防注入)
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,account);//設(shè)置參數(shù)
preparedStatement.setString(2,password);
ResultSet resultSet = preparedStatement.executeQuery();//執(zhí)行查詢sql,獲取結(jié)果集
輸出結(jié)果為false
赊抖,DB中執(zhí)行的SQL為
select file from file where name = '\'test\' or 1=1'
我們可以看到輸出的SQL是把整個(gè)參數(shù)用引號(hào)包起來统倒,并把參數(shù)中的引號(hào)作為轉(zhuǎn)義字符,從而避免了參數(shù)也作為條件的一部分
3. 源碼分析
結(jié)論
- preparedStatement.setString 會(huì)判斷當(dāng)前參數(shù)的符號(hào)是否需要轉(zhuǎn)義氛雪,是的話加的轉(zhuǎn)義符
- 如果不需要房匆,則直接加上引號(hào)
//完整代碼
public void setString(int parameterIndex, String x) throws SQLException {
synchronized (checkClosed().getConnectionMutex()) {
// if the passed string is null, then set this column to null
if (x == null) {
setNull(parameterIndex, Types.CHAR);
} else {
checkClosed();
int stringLength = x.length();
if (this.connection.isNoBackslashEscapesSet()) {
// Scan for any nasty chars
// 判斷是否需要轉(zhuǎn)義
boolean needsHexEscape = isEscapeNeededForString(x, stringLength);
if (!needsHexEscape) {
byte[] parameterAsBytes = null;
StringBuilder quotedString = new StringBuilder(x.length() + 2);
quotedString.append('\'');
quotedString.append(x);
quotedString.append('\'');
if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(quotedString.toString(), this.charConverter, this.charEncoding,
this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), getExceptionInterceptor());
} else {
// Send with platform character encoding
parameterAsBytes = StringUtils.getBytes(quotedString.toString());
}
setInternal(parameterIndex, parameterAsBytes);
} else {
byte[] parameterAsBytes = null;
if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(),
this.connection.parserKnowsUnicode(), getExceptionInterceptor());
} else {
// Send with platform character encoding
parameterAsBytes = StringUtils.getBytes(x);
}
setBytes(parameterIndex, parameterAsBytes);
}
return;
}