JDBC及防sql注入攻擊
哪有什麼一夜成名,都是百鍊成鋼
JDBC
JDBC(Java DataBase Connectivity,java資料庫連線)又SUN公司開發,是一種用於執行SQL語句的Java API,可以為多種關係資料庫提供統一訪問,它由一組用Java語言編寫的類和介面組成。JDBC提供了一種基準,據此可以構建更高階的工具和介面,使資料庫開發人員能夠編寫資料庫應用程式。
簡單地說,JDBC 可做三件事:與資料庫建立連線、傳送 操作資料庫的語句並處理結果。
程式碼示例:
Connection con = DriverManager.getConnection("jdbc:odbc:wombat","login", "password"); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1"); while (rs.next()) { int x = rs.getInt("a"); String s = rs.getString("b"); float f = rs.getFloat("c"); }
對以上程式碼分析
Connection 連線 代表了java和資料之間的通道,橋樑
Statement 語句 可以用來執行 insert, update, delete , select ...
ResultSet 結果集 代表的是查詢的結果
類
DriverManager 工具類,獲取連線物件
SQLException 異常物件 是 Exception的子型別,屬於檢查異常
操作步驟順序
- 載入驅動 (Driver) jdbc的驅動就是一個連線工廠,生成的產品是連線物件
com.mysql.jdbc.Driver 是Driver的mysql實現類
oracle.jdbc.Driver 是Driver的oracle實現類
Class.forName("驅動類名");
例如:
Class.forName("com.mysql.jdbc.Driver");
jdbc 3.0 以上版本都可以省略載入驅動這一步
- 獲取連線物件
DriverManager.getConnection(url, 使用者名稱, 密碼); // 內部呼叫了 Driver 物件獲取資料庫連線
url 的格式 :
jdbc:mysql://ip地址:埠號/資料庫名?引數
例如:mysql資料庫
Connection conn = DriverManager.getConnection ("jdbc:mysql://localhost:3306/test", "root", "root");
- 建立語句
Statement stmt = conn.createStatement();
- 執行sql
int rows = stmt.executeUpdate(); // 用來執行 insert , update, delete , DDL , 返回值代表影響行數
// delete from student where sid = 1008; 返回影響行數是0;
ResultSet rs = stmt.executeQuery(); // 用來執行 select
boolean moreRows = rs.next() 取得下一條記錄,
moreRows 是true ,表示有下一條記錄, false 表示沒有了
while(rs.next()) {
}
// 迭代器模式
- 釋放資源 ( 先開啟的資源後關閉 )
rs.close();
stmt.close();
conn.close();
JDBC及sql注入攻擊
1.建立一個user表,用來登入比對。
create table user(username varchar(50) not null,password varchar(50));
2.新增user資訊。
insert into user(username,password) values ('張三','123');
3.通過jdbc連線sql資料庫具體程式碼示例。
首先測試一下jdbc的連線,通過sql語句
select * from user where username='張三' and password='123';
來查詢出張三的賬號密碼
程式碼如下(通過字串拼接實現select傳參)
public class TestJdbc {
public static void main(String[] args) throws SQLException {
getSelect("張三","123");//使用者名稱 張三 密碼 123
return;
}
private static void getSelect(String name, String psd) throws SQLException {
//1.建立連線
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","root");
//2.準備sql語句
Statement statement = conn.createStatement();
//3.執行sql語句並返回一個集合
ResultSet resultSet = statement.executeQuery("select * from user where username='"+name+"' and password='"+psd+"'");
//4.如果有下一條資料返回true,沒有返回false
while (resultSet.next()){
//resultSet本身值在在第一條記錄的前面.
System.out.println(resultSet.getString("username")+resultSet.getString("password"));
}
statement.close();
conn.close();
}
}
輸出結果
張三123
上面我們傳的引數是‘張三’,‘123’ 都是正確的賬號密碼
那麼,接下來我們將引數換一下密碼看能否到正確的資訊
要替換的引數如下
getSelect("張三","'or '1'='1");
將密碼換成“'or ‘1’='1”。
public class TestJdbc {
public static void main(String[] args) throws SQLException {
getSelect("張三","'or '1'='1");//使用者,密碼
return;
}
private static void getSelect(String name, String psd) throws SQLException {
//1.建立連線
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","root");
//2.準備sql語句
Statement statement = conn.createStatement();
//3.執行sql語句並返回一個集合
ResultSet resultSet = statement.executeQuery("select * from user where username='"+name+"' and password='"+psd+"'");
//4.如果有下一條資料返回true,沒有返回false
while (resultSet.next()){
//resultSet本身值在在第一條記錄的前面.
System.out.println(resultSet.getString("username")+resultSet.getString("password"));
}
statement.close();
conn.close();
}
}
輸出結果
張三123
咦,到這裡是不是有點奇怪,明明密碼是123 隨便寫了個“'or ‘1’='1”也行,其實我們將我們的sql語句拿出來分析一下,答案顯而易見。
select * from user where username='"+name+"' and password='"+psd+"
這是我們的sql語句,我們將第一次的引數 “張三” “'or ‘1’='1”帶入得到新的sql.
select * from user where username='張三' and password=''or '1'='1'
看到這裡是不是發現了問題的真正所在,我們輸入的值直接將本來的sql語句都給發生了改變。
那麼,如何防治這種情況出現呢??
1.對引數記憶體做檢查,內部不能有sql關鍵字例如:or
2.用 PreparedStatement代替Statement
PreparedStatement 預編譯語句物件
Statement stmt = conn.createStatement();
stmt.executeUpdate(String sql);
- 需要預先提供sql語句
PreparedStatement psmt = conn.prepareStatement(String sql);
- 可以在sql中用?佔位某個值
insert into username(username,password) values(?, ?)
- 給?賦值
使用PreparedStatement中一系列以 set開頭的方法
setString(?的位置, 值)
setInt(?的位置, 值)
setDate(?的位置, 值)
psmt.setString(1, "張三");
psmt.setString(2, "123");
- 執行sql
psmt.executeUpdate();
注意: ?能夠佔位的只有值, 不能是表名、列名、關鍵字
5.用PreparedStatement的程式碼示例
public class TestJdbc {
public static void main(String[] args) throws SQLException {
getSelect("張三","123");//使用者,密碼
return;
}
private static void getSelect(String name, String psd) throws SQLException {
//1.建立連線
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","root");
//2.準備sql語句,用? 站位
String sql ="select * from user where username=? and password=?";
PreparedStatement statement = conn.prepareStatement(sql);
設定?的值,用順序去定位
statement.setString(1,name);
statement.setString(2,psd);
//3.執行sql語句並返回一個集合
ResultSet resultSet = statement.executeQuery();
//4.如果有下一條資料返回true,沒有返回false
while (resultSet.next()){
//resultSet本身值在在第一條記錄的前面.
System.out.println(resultSet.getString("username")
+resultSet.getString("password"));
}
statement.close();
conn.close();
}
}