JDBC:使用Statement引发SQL注入

1. 什么是 SQL 注入?

jdbc程序执行时, sql语句在拼接时由页面传入参数,如果用户恶意传入一些sql中的特殊关键字,会导致sql语句意义发生变化,这种攻击方式就叫做sql注入。

2. 引子:

sql注入的危害: 黑客可以一行代码登录超管账户,对数据库造成不可挽回的损失。


参考用户注册登录案例:

// 用户登录验证(字符串拼接)
String sql = "select * from s_user where loginName = '"+ loginName +"' and loginPwd = '"+ loginPwd +"'";
// 正常用户:
用户名:admin
密  码:123456
可以正常登录
 ---------------------------------------------
 // 恶意用户:
 用户名: aaa   
 密  码: aaa' or '1'='1  
 也可以登陆成功, 这叫做SQL注入, 

由于 恶意用户 输入的密码被当作sql语句,编译时,1=1,返回true, 所以验证通过,登陆成功。


3. 具体实例

下方的登录实例,运行成功后,后台输入恶意sql语句,就可以登陆成功。

public class JDBCTest02 {
    public static void main(String[] args) {
        // 初始化界面,(返回用户名/密码)
        Map<String,String> userLoginInfo = initUi();
        // 验证用户名和密码:(传入登录信息)
        boolean loginSuccess = login(userLoginInfo);
        // 登陆成功/失败 ==> 布尔值
        // 最后输出结果
        System.out.println(loginSuccess ? "登陆成功": "登陆失败");
    }

    /*
    * 用户登录:
    * @param userLoginInfo 用户登录信息
    * @return false 登陆失败, true 登陆成功
    */
    private static boolean login(Map<String, String> userLoginInfo) {
        // JDBC 代码
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        // 单独定义变量
        String loginName = userLoginInfo.get("loginName");
        String loginPwd  = userLoginInfo.get("loginPwd");
        // 打标记意识
        boolean loginSuccess = false;

        try {
            // 1. 注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 2. 获取数据库连接
            // 使用时,把school改为你自己的数据库名
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root","root");
            // 3. 获取数据库操作对象
            stmt = conn.createStatement();
            // 4. 执行SQL
            // 注意: s_user是school里的用户表,记得改为你自己定义的的用户表!!
            String sql = "select * from s_user where loginName = '"+ loginName +"' and loginPwd = '"+ loginPwd +"'";
            rs = stmt.executeQuery(sql);
            // 5. 处理结果集
            if (rs.next()){
                // 登陆成功
                loginSuccess = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            // 6. 释放资源
            if (rs !=null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt !=null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn !=null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        // return false;
        return loginSuccess;
    }
    /*
    * 初始化用户界面
    * @return 用户输入的用户名和密码等登录信息
    *
    */
    private static Map<String, String> initUi() {
        Scanner s = new Scanner(System.in);
        System.out.println("用户名: ");
        String loginName = s.nextLine();
        System.out.println("密码: ");
        String loginPwd = s.nextLine();
        // 用键值对存储输入信息
        Map<String,String> userLoginInfo = new HashMap<>();
        // 传值
        userLoginInfo.put("loginName", loginName);
        userLoginInfo.put("loginPwd", loginPwd);
        return userLoginInfo;
    }
}

4. 如何防止sql注入?

原理: 让用户输入信息不参与SQL语句的编译过程,问题就解决了。
即使用户提供的信息中含有SQL语句的关键字,但是无法参与编译,也不起作用。

这里引入 StatementPreparedStatement

  1. 上面的案例使用了数据库操作对象 Statement ,让sql语句直接参与编译。
  2. SUN公司发明了 PreparedStatement 来让sql语句预编译,然后再传值,间接的阻止了恶意的sql注入。

5. PreperedStatement 相对 Statement 的优点:

  1. 没有SQL注入的问题。
  2. Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。
  3. 数据库和驱动可以对PreperedStatement进行优化(只有在相关联的数据库连接没有关闭的情况下有效)。

6. 总结

SQL注入虽然十几年前就被淘汰了,但是现在仍然有一些程序员偷懒,粗心大意,写了一些带有漏洞的代码,让黑客有机可乘,显得程序猿很不专业,奉劝在座的各位,耗子喂汁。

上一篇:Laravel整合gRPC


下一篇:WPF+MVVM基础登录