前沿
在現有的框架中sql防注入已經做得很好了,我們需要做的就是儘量不要使用sql拼接調用
java sql注入原因以及預防方案(易理解)
1. SQL注入
1.1 原理
SQL注入是通過客戶端的輸入把SQL命令注入到一個應用的資料庫中,從而執行惡意的SQL語句。
1.2 演示
1.2.1 案例1
有一個登錄框,需要 輸入用戶名和密碼 ,然後我們的密碼輸入 'or '123' = '123 這樣的。我們在查詢用戶名和密碼是否正確的時候,本來執行的sql語句是:select * from user where username = '' and password = ''. 這樣的sql語句,現在我們輸入密碼是如上這樣的,然後我們會通過參數進行拼接,拼接後的sql語句就是:
select * from user where username = '' and password = ' ' or '123' = '123 ';這樣的了,那麼會有一個or語句,只要這兩個有一個是正確的話,就條件成立,因此 123 = 123 是成立的。因此驗證就會被跳過。這只是一個簡單的例子,
1.2.2 案例2
密碼比如是這樣的:'; drop table user;, 這樣的話,那麼sql命令就變成了:
select * from user where username = '' and password = ''; drop table user;', 那麼這個時候我們會把user表直接刪除了。
1.3 防範
1.3.1 前端
前端表單進行參數格式控制;
1.3.2 後端
我們可以使用預編譯語句(PreparedStatement,這 樣的話即使我們使用sql語句偽造成參數,到了服務端的時候,這個偽造sql語句的參數也只是簡單的字符,並不能起到攻擊的作用。 使用正則表達式過濾傳入的參數注意: 永遠也不要把未經檢查的用戶輸入的值直接傳給資料庫
package cn.javanode.thread; import java.util.regex.Pattern; /** * @author xgt(小光頭) * @version 1.0 * @date 2021-1-8 11:48 */ public class CheckSqlDemo { /**正則表達式**/ 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")); } }
補充
PreparedStatement是如何防止SQL注入的?
1. 拼接參數(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參數 ResultSet resultSet = preparedStatement.executeQuery(); System.out.println(resultSet.next());
輸出結果為 true ,DB中執行的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);//設置參數 preparedStatement.setString(2,password); ResultSet resultSet = preparedStatement.executeQuery();//執行查詢sql,獲取結果集
輸出結果為 false ,DB中執行的SQL為
select file from file where name = '\'test\' or 1=1'
我們可以看到輸出的 SQL是把整個參數用引號包起來,並把參數中的引號作為轉義字符,從而避免了參數也作為條件的一部分
3. 源碼分析
結論
preparedStatement.setString 會判斷當前參數的符號是否需要轉義,是的話加的轉義符 如果不需要,則直接加上引號//完整代碼 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 // 判斷是否需要轉義 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; }
【編輯推薦】
【責任編輯:
姜華TEL:(010)68476606】