1 什麼是SQL攻擊
在需要用戶輸入的地方,用戶輸入的是SQL語句的片段,最終用戶輸入的SQL片段與我們DAO中寫的SQL語句合成一個完整的SQL語句!例如用戶在登錄時輸入的用戶名和密碼都是爲SQL語句的片段!
2 演示SQL攻擊
首先我們需要創建一張用戶表,用來存儲用戶的信息。
CREATE TABLE user( uid CHAR(32) PRIMARY KEY, username VARCHAR(30) UNIQUE KEY NOT NULL, PASSWORD VARCHAR(30) );
INSERT INTO user VALUES('U_1001', 'zs', 'zs'); SELECT * FROM user; |
現在用戶表中只有一行記錄,就是zs。
下面我們寫一個login()方法!
public void login(String username, String password) { Connection con = null; Statement stmt = null; ResultSet rs = null; try { con = JdbcUtils.getConnection(); stmt = con.createStatement(); String sql = "SELECT * FROM user WHERE " + "username='" + username + "' and password='" + password + "'"; rs = stmt.executeQuery(sql); if(rs.next()) { System.out.println("歡迎" + rs.getString("username")); } else { System.out.println("用戶名或密碼錯誤!"); } } catch (Exception e) { throw new RuntimeException(e); } finally { JdbcUtils.close(con, stmt, rs); } } |
下面是調用這個方法的代碼:
login("a' or 'a'='a", "a' or 'a'='a"); |
這行當前會使我們登錄成功!因爲是輸入的用戶名和密碼是SQL語句片段,最終與我們的login()方法中的SQL語句組合在一起!我們來看看組合在一起的SQL語句:
SELECT * FROM tab_user WHERE username='a' or 'a'='a' and password='a' or 'a'='a' |
3 防止SQL攻擊
- 過濾用戶輸入的數據中是否包含非法字符;
- 分步交驗!先使用用戶名來查詢用戶,如果查找到了,再比較密碼;
- 使用PreparedStatement。
4 PreparedStatement是什麼?
PreparedStatement叫預編譯聲明!
PreparedStatement是Statement的子接口,你可以使用PreparedStatement來替換Statement。
PreparedStatement的好處:
- 防止SQL攻擊;
- 提高代碼的可讀性,以可維護性;
- 提高效率。
5 PreparedStatement的使用
- 使用Connection的prepareStatement(String sql):即創建它時就讓它與一條SQL模板綁定;
- 調用PreparedStatement的setXXX()系列方法爲問號設置值
- 調用executeUpdate()或executeQuery()方法,但要注意,調用沒有參數的方法;
String sql = “select * from tab_student where s_number=?”; PreparedStatement pstmt = con.prepareStatement(sql); pstmt.setString(1, “S_1001”); ResultSet rs = pstmt.executeQuery(); rs.close(); pstmt.clearParameters(); pstmt.setString(1, “S_1002”); rs = pstmt.executeQuery(); |
在使用Connection創建PreparedStatement對象時需要給出一個SQL模板,所謂SQL模板就是有“?”的SQL語句,其中“?”就是參數。
在得到PreparedStatement對象後,調用它的setXXX()方法爲“?”賦值,這樣就可以得到把模板變成一條完整的SQL語句,然後再調用PreparedStatement對象的executeQuery()方法獲取ResultSet對象。
注意PreparedStatement對象獨有的executeQuery()方法是沒有參數的,而Statement的executeQuery()是需要參數(SQL語句)的。因爲在創建PreparedStatement對象時已經讓它與一條SQL模板綁定在一起了,所以在調用它的executeQuery()和executeUpdate()方法時就不再需要參數了。
PreparedStatement最大的好處就是在於重複使用同一模板,給予其不同的參數來重複的使用它。這纔是真正提高效率的原因。
所以,建議大家在今後的開發中,無論什麼情況,都去需要PreparedStatement,而不是使用Statement。
package cn.itcast.demo3;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.junit.Test;
/**
* PreapredStatement
* 防SQL攻擊
* @author cxf
*
*/
public class Demo3 {
/**
* 登錄
* 使用username和password去查詢數據
* 若查出結果集,說明正確!返回true
* 若查出不出結果,說明用戶名或密碼錯誤,返回false
* @param username
* @param password
* @return
* @throws Exception
*/
public boolean login(String username, String password) throws Exception {
/*
* 一、得到Connection
* 二、得到Statement
* 三、得到ResultSet
* 四、rs.next()返回的是什麼,我們就返回什麼
*/
// 準備四大參數
String driverClassName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/mydb3";
String mysqlUsername = "root";
String mysqlPassword = "123";
// 加載驅動類
Class.forName(driverClassName);
// 得到Connection
Connection con = DriverManager.getConnection(url, mysqlUsername, mysqlPassword);
// 得到Statement
Statement stmt = con.createStatement();
// 給出sql語句,調用stmt的executeQuery(),得到ResultSet
String sql = "select * from t_user where username='" + username + "' and password='" + password + "'";
System.out.println(sql);
ResultSet rs = stmt.executeQuery(sql);
return rs.next();
}
/**
* SQL攻擊!
* @throws Exception
*/
@Test
public void fun1() throws Exception {
//select * from t_user where username='a' or 'a'='a' and password='a' or 'a'='a'
String username = "a' or 'a'='a";
String password = "a' or 'a'='a";
boolean bool = login(username, password);
System.out.println(bool);
}
public boolean login2(String username, String password) throws Exception {
/*
* 一、得到Connection
* 二、得到Statement
* 三、得到ResultSet
* 四、rs.next()返回的是什麼,我們就返回什麼
*/
// 準備四大參數
String driverClassName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/mydb3?useServerPrepStmts=true&cachePrepStmts=true";
String mysqlUsername = "root";
String mysqlPassword = "123";
// 加載驅動類
Class.forName(driverClassName);
// 得到Connection
Connection con = DriverManager.getConnection(url, mysqlUsername, mysqlPassword);
///////////////////////////////////////
///////////////////////////////////////
/*
* 一、得到PreparedStatement
* 1. 給出sql模板:所有的參數使用?來替代
* 2. 調用Connection方法,得到PreparedStatement
*/
String sql = "select * from t_user where username=? and password=?";
PreparedStatement pstmt = con.prepareStatement(sql);
/*
* 二、爲參數賦值
*/
pstmt.setString(1, username);//給第1個問號賦值,值爲username
pstmt.setString(2, password);//給第2個問號賦值,值爲password
ResultSet rs = pstmt.executeQuery();//調用查詢方法,向數據庫發送查詢語句
pstmt.setString(1, "liSi");
pstmt.setString(2, "123");
pstmt.executeQuery();
return rs.next();
}
@Test
public void fun2() throws Exception {
//select * from t_user where username='a' or 'a'='a' and password='a' or 'a'='a'
String username = "zhangSan";
String password = "123";
boolean bool = login2(username, password);
System.out.println(bool);
}
/**
* 測試JdbcUtils.getConnection()
* @throws SQLException
* @throws ClassNotFoundException
* @throws IOException
*/
@Test
public void fun3() throws SQLException {
Connection con = JdbcUtils.getConnection();
System.out.println(con);
Connection con1 = JdbcUtils.getConnection();
System.out.println(con1);
}
}