只要你學JDBC,基本上所有的人都會和你說,Statement不能防止SQL注入, PreparedStatement能夠防止SQL注入.

 

基本上參加工作了一段時間之後還是這麼認為的, 沒錯, 這句是沒有問題的, 但到底如何進行SQL注入?怎麼直觀的去瞭解SQL注入?這還是需要花一定的時間去實驗的.



 

前提:以下的測試都是在一種理想環境下



 

首先准備好數據庫環境, 以下是數據庫的schema:

 

create database java_mysql;

 

use java_mysql;

 

drop table if exists pstest;

 

create table pstest(
id int(10) not null primary key auto_increment,
name Varchar(32),
age int(3)
);

 

insert into pstest (name, age) values ('Tom', 23);
insert into pstest (name, age) values ('Tom1', 23);
insert into pstest (name, age) values ('Tom2', 23);
insert into pstest (name, age) values ('Tom3', 23);
insert into pstest (name, age) values ('Tom4', 23);
insert into pstest (name, age) values ('Tom5', 23);




 

以上就是建立了pstest表, 並插入了一些測試數據.




 

1. 測試Statement

 

public class StatementTest {
public static void main(String[] args) throws SQLException {
Connection con = null;
Statement stmt = null;
// name很強大, 傳入了這麼多東西
String name = "Tom';delete from pstest;select * from pstest where name='Tom";
String sql = createSql(name); // SQL
System.out.println(sql);

 

try {
con = DBConn.getConnection();
stmt = con.createStatement();

 

stmt.execute(sql);
} catch(Exception e) {
e.printStackTrace();
} finally {
stmt.close();
con.close();
}
}

 

// 根據參數的name參數查詢
private static String createSql(String name) {
String sql = "select id, name, age from pstest ";

 

// 拼接一下SQL
if(name != null && name.length() != 0) {
sql += "where name ='" + name + "'";
}

 

return sql;
}
}





 

數據庫連接的URL為:

 

"jdbc:mysql://localhost:3306/java_mysql";


 

這裏將URL單獨拎出來是有作用的, 繼續看下面




 

Run一下StatementTest. 會發現報異常了:

 

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'delete from pstest;select * from pstest where name='XX'' at line 1



 

看到這裏,你也應該想到了Statement的execute(String sql)默認是只能執行一條SQL的.




 

若想讓execute(String sql)能夠同時只能幾條SQL語句, 怎麼辦?修改連接的URL:




 

jdbc:mysql://localhost:3306/java_mysql?allowMultiQueries=true


 

重點是allowMultiQueries=true這個參數
再來Run一下StatementTest. OK,沒有報任何的異常.列印的SQL為:

 

select id, name, age from pstest where name ='Tom';delete from pstest;select * from pstest where name='Tom';

 

上面的SQL到底做了什麼呢?
我們先觀察一下:第一條是查詢SQL, 第二條是delete, 第三條是查詢SQL,其實第一條和第三條是一樣的.

 

為了直觀的看上面SQL的執行效果, 我們再次執行下最開始的schema.sql

 

這時候數據庫有6條數據

 

mysql> select * from pstest;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | Tom | 23 |
| 2 | Tom1 | 23 |
| 3 | Tom2 | 23 |
| 4 | Tom3 | 23 |
| 5 | Tom4 | 23 |
| 6 | Tom5 | 23 |
+----+------+------+




 

執行一下




 

select id, name, age from pstest where name ='Tom';delete from pstest;select * from pstest where name='Tom';



 

這時候可以看到MySQL客戶端

 

mysql> select id, name, age from pstest where name ='Tom';delete from pstest;select * from pstest where name='Tom';
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | Tom | 23 |
+----+------+------+
1 row in set (0.00 sec) -- 執行第一條查詢SQL
 
Query OK, 6 rows affected (0.05 sec) -- 執行第二條delete語句, Oh, No, 數據庫全部的6條數據被刪除了

 

Empty set (0.00 sec) -- 執行第三條SQL查詢, 沒有查詢到任何數據


 

上面的注釋已經寫好了,就不多說了。




 

這時候的確已經實現了SQL注入.







 

2. 測試PreparedStatemet

 

public class PreparedStatementTest {
public static void main(String[] args) throws SQLException {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select id, name, age from pstest where name = ? ";

 

try {
con = DBConn.getConnection();
ps = con.prepareStatement(sql);
ps.setString(1, "Tom';delete from pstest;select * from pstest where name='Tom");
rs = ps.executeQuery();
} catch(Exception e) {
e.printStackTrace();
} finally {
rs.close();
ps.close();
con.close();
}
}
}





 

直接Run一下,OK,也沒出現任何的異常,數據庫中的數據也還在

 

但是我們到底執行了什麼樣的SQL, 查看MySQL的日誌.

 

這裏簡單提下MySQL的日誌,可以在my.ini下配置

 

[mysqld]
log=MySQL_Log # 在這裏加上日誌名稱</span>
這時候會在MySQL/MySQL Server 5.1/Data/目錄下生成MySQL_Log檔, 裏面記錄的就是日誌了.



 

好了,回到正題,看一下剛剛PreparedStatementTest執行的SQL,在MySQL_Log中查看






 

120719 15:54:25 23 Connect root@localhost on java_mysql
23 Query /* mysql-connector-java-5.1.20-SNAPSHOT ( Revision: ${bzr.revision-id} ) ...
23 Query SHOW WARNINGS
23 Query /* mysql-connector-java-5.1.20-SNAPSHOT ( Revision: ${bzr.revision-id} ) */SELECT @@session.auto_increment_increment
23 Query SHOW COLLATION
23 Query SET character_set_results = Null
23 Query SET autocommit=1
23 Query select id, name, age from pstest where name = 'Tom\';delete from pstest;select * from pstest where name=\'XX'
23 Quit


 

重點是最後一條SQL.

 

因為數據庫中的數據都還在,我們就直接執行這條SQL




 

select id, name, age from pstest where name = 'Tom\';delete from pstest;select * from pstest where name=\'Tom'



 

查看MySQL的客戶端

 

mysql> select id, name, age from pstest where name = 'Tom\';delete from pstest;select * from pstest where name=\'XX';
Empty set (0.00 sec) -- 就執行了這一條查詢的SQL語句



 

可以看到什麼的字串已經被轉義了.

 

讓我們來看一下轉義字元:

 

mysql> select 'Tom\';delete';
+-------------+
| Tom';delete |
+-------------+
| Tom';delete |
+-------------+
1 row in set (0.00 sec)





 

總結:其實從上面的測試中已經看出了。的確Statement是不安全的, 可以進行SQL注入, 而PreparedStatement可以防止SQL注入。就好比上面我們想在做查詢的時候將pstest中的全部數據都刪除掉一樣. 前面已經說過這是在理想的環境下做的測試. 在真正的環境中,就想這麼簡單的實現SQL注入, 基本上是不可能的。而且Statemenet,讓它執行execute(String sql)的時候同時執行多條SQL, 基本上不可能會去這麼做的.




 

其實,關於Statement的execute(String sql)語句能夠同時執行多條SQL語句, 可以看MySQL自帶的測試例子:

 

可查看testsuite.regression包下的ResultSetRegressionTest類:

 

public class ResultSetRegressionTest extends BaseTestCase {
public void testBug33678() throws Exception {
if (!versionMeetsMinimum(4, 1)) {
return;
}

 

createTable("testBug33678", "(field1 INT)");

 

// allowMultiQueries=true設置
Connection multiConn = getConnectionWithProps("allowMultiQueries=true");
Statement multiStmt = multiConn.createStatement();

 

try {
multiStmt.setFetchSize(Integer.MIN_VALUE);

 

// 一次性執行多條SQL語句
multiStmt
.execute("SELECT 1 UNION SELECT 2; INSERT INTO testBug33678 VALUES (1); UPDATE testBug33678 set field1=2; INSERT INTO testBug33678 VALUES(3); UPDATE testBug33678 set field1=2 WHERE field1=3; UPDATE testBug33678 set field1=2; SELECT 1");

 

// 以下代碼省略...
}
}
 
 
 
 
 
 
arrow
arrow
    全站熱搜

    戮克 發表在 痞客邦 留言(0) 人氣()