Sql代码
- 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
Java代码
- 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为:
Java代码
- "jdbc:mysql://localhost:3306/java_mysql";
其实上面的意图很简单:
Tom';delete from pstest;select * from pstest where name='Tom
就是想先执行一条SQL查询语句,然后把表的数据删除。
这只是理想环境. 实际上要想传入这么复杂的数据, 真的很难想象
这里将URL单独拎出来是有作用的, 继续看下面
Run一下StatementTest. 会发现报异常了:
Java代码
- 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='Tom'' at line 1
想法很简单, 现实很残酷, 未能如所愿.
看到这里,你也应该想到了Statement的execute(String sql)默认是只能执行一条SQL的.
若想让execute(String sql)能够同时只能几条SQL语句, 怎么办?修改连接的URL:
Java代码
- jdbc:mysql://localhost:3306/java_mysql?allowMultiQueries=true
重点是allowMultiQueries=true这个参数
再来Run一下StatementTest. OK,没有报任何的异常.打印的SQL为:
Sql代码
- select id, name, age from pstest where name ='Tom';delete from pstest;select * from pstest where name='Tom';
我们先观察一下:第一条是查询SQL, 第二条是delete, 第三条是查询SQL,其实第一条和第三条是一样的.
为了直观的看上面SQL的执行效果, 我们再次执行下最开始的schema.sql
这时候数据库有6条数据
Sql代码
- 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 |
- +----+------+------+
执行一下
Sql代码
- select id, name, age from pstest where name ='Tom';delete from pstest;select * from pstest where name='Tom';
这时候可以看到MySQL客户端
Sql代码
- 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
Java代码
- 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下配置
Sql代码
- [mysqld]
- log=MySQL_Log # 在这里加上日志名称
好了,回到正题,看一下刚刚PreparedStatementTest执行的SQL,在MySQL_Log中查看
Sql代码
- 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='Tom'
- 23 Quit
重点是最后一条SQL.
因为数据库中的数据都还在,我们就直接执行这条SQL
Sql代码
- select id, name, age from pstest where name = 'Tom';delete from pstest;select * from pstest where name='Tom'
查看MySQL的客户端
Sql代码
- mysql> select id, name, age from pstest where name = 'Tom';delete from pstest;select * from pstest where name='Tom';
- Empty set (0.00 sec) -- 就执行了这一条查询的SQL语句
可以看到什么的字符串已经被转义了.
让我们来看一下转义字符:
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类:
Java代码
- 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");
- // 以下代码省略...
- }
- }