sql注入现象

sql注入现象:

1、先用一个程序引出sql注入现象:
例如:模拟用户登录业务。

测试代码:

package sql_inject;

import java.util.HashMap;
import java.util.Map;
import java.sql.*;
import java.util.Scanner;

public class Test01 {
    public static void main(String[] args) {

        // 用户信息初始化
        Map<String, String> userInfo = initUI();

        // 检验用户名和密码
        boolean loginSuccess = login(userInfo);

        // 处理检验结果
        System.out.println(loginSuccess ? "登录成功" : "登录失败");

    }

    // 用户信息初始化
    private static Map<String, String> initUI() {

        Scanner sc = new Scanner(System.in);

        System.out.print("请输入用户名:");
        String userName = sc.nextLine();

        System.out.print("请输入密码:");
        String password = sc.nextLine();

        Map<String, String> map = new HashMap<>();
        map.put("userName", userName);
        map.put("password", password);

        return map;
    }

    // 检验用户名和密码
    private static boolean login(Map<String, String> userInfo) {

        // 定义标记,检验的结果
        boolean loginSuccess = false;

        // 获取登录信息中的用户名,密码
        String loginName = userInfo.get("userName");
        String loginPwd = userInfo.get("password");

        // 连接数据库的配置信息
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://127.0.0.1:3306/study_test";
        String user = "root";
        String password = "666666";

        Connection conn = null;
        Statement stmt = null;
        ResultSet result = null;

        try {
            // 1、注册驱动
            Class.forName(driver);

            // 2、获取连接
            conn = DriverManager.getConnection(url, user, password);

            // 3、获取数据库操作对象
            stmt = conn.createStatement();

            // 4、执行sql语句
            String sql = "select * from t_user where username = '"+ loginName +"' and pwd = '"+ loginPwd +"'";
            result = stmt.executeQuery(sql);

            // 5、处理查询结果集
            if (result.next()) {
                loginSuccess = true;
            }

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源
            if (result != null) {
                try {
                    result.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }

}

当前程序存在的问题(用下列信息也能登陆成功):
用户名:abc
密码:abc’ or ‘1’='1
登录结果:登录成功

即拼接的sql语句为:select * from t_user where username = ‘abc’ and pwd = ‘abc’ or ‘1’=‘1’;
这条语句恒成立,因为条件使用了or true。

这种现象被称为:sql注入现象。(黑客经常使用)

sql注入现象的根本原因是什么?
用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程。导致sql语句的原意被扭曲,进而达到sql注入。


2、怎么解决sql注入现象?

(1)只要用户输入的内容不参与sql的编译过程,问题就得到了解决。
即使用户输入的信息含有sql关键字,没有参与编译过程,不起作用。

(2)具体怎么操作代码才能解决sql注入问题?
只需将数据库操作对象接收的类型Statement换成PreparedStatement即可。

PreparedStatement接口继承了Statement接口,同样也在java.sql.*包下。
PreparedStatement属于预编译的数据库操作对象。

它的原理是:预先对sql语句的框架进行编译,然后再给sql语句传值。

具体操作代码为:
// 第3步:获取预编译的数据库连接对象
String sql = “select username, pwd from t_user where username = ? and pwd = ?”;
PreparedStatement ps = conn.prepareStatement(sql);

// 给占位符传值(JDBC中所有的下标从1开始,第1个问号的下标是1)
ps.setString(1, loginName);
ps.setString(2, loginPwd);
// 如果是其他类型的值,只需要调用ps的setInt(), setDouble()等方法。

// 第4步:执行sql语句
ResultSet result = ps.executeQuery(); // 注意没有参数,已经指定了sql语句

其中sql语句中的?属于占位符,一个占位符一个值。
注意:不要写成了’?’, 这个属于单个字符。

测试结果:
用户名:abc
密码:abc’ or ‘1’='1
登录结果:登录失败

测试代码:

package sql_inject;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Test02 {
    public static void main(String[] args) {

        // 用户信息初始化
        Map<String, String> userInfo = initUI();

        // 检验用户名和密码
        boolean loginSuccess = login(userInfo);

        // 处理检验结果
        System.out.println(loginSuccess ? "登录成功" : "登录失败");

    }

    // 用户信息初始化
    private static Map<String, String> initUI() {

        Scanner sc = new Scanner(System.in);

        System.out.print("请输入用户名:");
        String userName = sc.nextLine();

        System.out.print("请输入密码:");
        String password = sc.nextLine();

        Map<String, String> map = new HashMap<>();
        map.put("userName", userName);
        map.put("password", password);

        return map;
    }

    // 检验用户名和密码
    private static boolean login(Map<String, String> userInfo) {

        // 定义标记,检验的结果
        boolean loginSuccess = false;

        // 获取登录信息中的用户名,密码
        String loginName = userInfo.get("userName");
        String loginPwd = userInfo.get("password");

        // 连接数据库的配置信息
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://127.0.0.1:3306/study_test";
        String user = "root";
        String password = "666666";

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet result = null;

        try {
            // 1、注册驱动
            Class.forName(driver);

            // 2、获取连接
            conn = DriverManager.getConnection(url, user, password);

            // 3、获取数据库操作对象
            String sql = "select * from t_user where username = ? and pwd = ?";
            ps = conn.prepareStatement(sql);  // 绑定需要预编译的语句

            // 给占位符指定值
            ps.setString(1, loginName);
            ps.setString(2, loginPwd);

            // 4、执行sql语句
            result = ps.executeQuery();

            // 5、处理查询结果集
            if (result.next()) {
                loginSuccess = true;
            }

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源
            if (result != null) {
                try {
                    result.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }
}

(3)对比Statement和PreparedStatement用在登录业务上面的区别:
1、Statement存在sql注入现象,PreparedStatement解决了sql注入问题。
2、Statement是编译一次,执行一次,因为每个人的账号和密码都不一样,sql语句不一样。
PreparedStatement是编译一次,执行n次,采用预编译,语句没改动,不用持续编译,效率较高。
3、PreparedStatement在编译阶段,会对传值的类型进行安全检查。

综上所述:PreparedStatement使用较多,只有极少数情况才用Statement。


4、那什么情况使用Statement呢?
业务要求使用sql注入的时候,此时必须使用Statement。

例如,有个按钮的功能是:按照商品价格升序。
此时会在sql语句的最后面拼接关键字:order by price desc。

如果使用的是PreparedStatement的话,在传值的时候出现的问题:
ps.getString(1, “desc”);
因为desc是一个字符串,只能用getString传值,那么最后sql语句变为:order by price ‘desc’。
这样的语句能执行吗?显然不行。


3、练习:PreparedStatement完成增删改。

测试代码:

package sql_inject;
import java.sql.*;

public class Test03 {
    public static void main(String[] args) {

        // 先准备连接数据库的配置信息
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://127.0.0.1:3306/study_test";
        String user = "root";
        String password = "666666";

        Connection conn = null;
        PreparedStatement ps = null;

        try {
            // 1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");

            // 2、获取连接
            conn = DriverManager.getConnection(url, user, password);

            // 3、获取预处理数据库操作对象
            /*String sql = "insert into dept(deptno, dname, loc) values(?, ?, ?)";
            ps = conn.prepareStatement(sql);
            ps.setInt(1, 50);  // 部门编号
            ps.setString(2, "人事部");  // 部门名称
            ps.setString(3, "北京");  // 部门位置*/

            /*String sql = "update dept set loc = ? where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, "天津");
            ps.setInt(2, 50);*/

            String sql = "delete from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setInt(1, 50);

            // 4、执行sql语句
            int line = ps.executeUpdate();
            System.out.println(line == 1 ? "成功" : "失败");

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            // 6、释放资源
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }

    }
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值