狭义的 SQL注入

称之为狭义,是指我在深入了解前自己对SQL注入的理解,也就是在练习JDBC操作数据库时接触到的SQL注入。

1.1 JDBC

先介绍一下JDBC的概念:JDBC是sun公司(已被Oracle收购)制定一系列接口标准,由不同数据库厂商(Oracle、MySQL等)实现接口方法并封装成驱动文件,供开发人员操作数据库。也就是说,开发人员可采用统一的代码来操作不同的数据库,而无需关注驱动文件的内部实现。

通俗的理解就是JDBC是规范、是接口,不同的数据库厂商要据此编写各自的操作实现。

1.2 SQL注入

我们知道,dao层(负责操作数据库)中执行的SQL语句是拼接出来的,其中有一部分数据是由用户从客户端传入,所以当用户传入的数据中包含SQL关键字时,就有可能通过这些关键字改变SQL语句的语义,从而执行一些特殊的操作,这样的攻击方式就叫做SQL注入攻击

例如以下用户登录的代码:

public boolean login(String username, String password){ 
        Connection conn = null; 
        Statement stmt = null; 
        ResultSet rs = null; 
        if (username == null || password == null){ 
            return false; 
        } 
        try { 
            conn = JDBCUtils.getConnection();     //获取数据库链接对象 
            String sql = "select * from user where username = '" + username + "'and password = '" + password +"'" ; 
            stmt = conn.createStatement(); 
            rs = stmt.executeQuery(sql); 
            return rs.next(); 
        } catch (SQLException e) { 
            e.printStackTrace(); 
        }finally { 
            JDBCUtils.close(rs,stmt,conn);  //释放资源 
        } 
        return false; 
    }

程序的逻辑为

  1. 用户输入用户名和密码
  2. 在数据库中查找匹配的用户名和密码,若有匹配的则登录成功,否则,提示"用户名或密码错误!"。实际就是在数据库中执行以下sql语句
1. String sql = “select * from user where username = '” + username + “'and password = '” + password +"’" ;

上边代码的逻辑看似没有问题,实则问题很大:

当用户这样输入:一个数据库中不存在的用户名,例如bbb,然后输入密码:a' or 'a' = 'a,这样输入竟然也能登陆成功。

原因:
当用户输入以上用户名和密码时,程序就会在数据库中执行以下sql语句
sql :select * from user where username = 'bbb' and password = 'a' or 'a' = 'a' ;

这是一条恒等式,数据库会返回user表中所有的内容,这样rs.next()就会等于true,所以可以登陆成功。

1.3 SQL注入的预防

使用PreparedStatement代替Statement可以有效防止SQL注入的发生。

PreparedStatement利用预编译的机制将sql语句的主干和参数分别传输给数据库服务器,从而使数据库分辨的出哪些是sql语句的主干哪些是参数,这样一来即使参数中带了sql的关键字,数据库服务器也仅仅将他当作参数值使用,关键字不会起作用,从而从原理上防止了sql注入的问题。

例如,上面那条SQl语句就要改成:

String sql = "select * from user where username = ? and password = ?" ;

再去给每个 ?占位符赋值

ps.setString(1, name); 
 ps.setInt(2, password);

PreparedStatement主要有如下的三个优点:

  1. 可以防止sql注入
  2. 由于使用了预编译机制,执行的效率要高于Statement
  3. sql语句使用?形式替代参数,然后再用方法设置?的值,比起拼接字符串,代码更加优雅.

PreparedStatement 与Statment比较:

  1. 语法不同:PreparedStatement可以使用预编译的sql,而Statment只能使用静态的sql
  2. 效率不同: PreparedStatement可以使用sql缓存区,效率比Statment高
  3. 安全性不同: PreparedStatement可以有效防止sql注入,而Statment不能防止sql注入。