JDBC3——SQL隱碼攻擊、及其解決方法——Statement與PreparedStatement對比——PreparedStatement的CRUD

丟丟diu丟發表於2020-10-17

1.使用者登陸系統演示—Statement—SQL隱碼攻擊

1.1.需求:
  •  模擬使用者登陸功能的實現
    
  • 使用者名稱:zhangsan
  • 密碼:123
1.2.業務描述:
  •  程式執行的時候,提供一個輸入的入口,可以讓使用者輸入使用者名稱、密碼
     輸入後,提交資訊,java程式蒐集到使用者資訊
    
  •  JAVA程式連線資料庫驗證是否合法
     	 合法:顯示登陸成功
     	 非法:顯示登陸失敗
    
1.3.本程式存在的問題—— 存在SQL語句注入現象
  • 使用者名稱:a
  • 密碼:b’ or ‘1’='1
  • 會登入成功
1.4.導致SQL語句注入的根本原因:
  • 使用者輸入的東西,含有sql關鍵字,而且編譯進去了,導致系統原來的意思被扭曲!!!!
1.5.解決方法:
  • 只要使用者輸入的SQL關鍵字不編譯進去就可以了
    1.6.執行結果
  • 正確例項:
    在這裡插入圖片描述
  • 錯誤結果:
    在這裡插入圖片描述
public class JDBCTestSqlInject {
    public static void main(String[] args) {
        //1.輸入資訊
        Map<String, String> userLoginInfo = inintUI();

        //2.驗證資訊是否正確
        boolean loginSuccess = login(userLoginInfo);
        System.out.println(loginSuccess ? "登陸成功" : "登陸失敗");

    }
        /**
         * 驗證使用者名稱,密碼是否正確
         * @param userLoginInfo
         * @return
         */
        private static boolean login (Map < String, String > userLoginInfo){
            boolean flag=false;
            String  loginName = userLoginInfo.get("loginName");
            String  loginPwd = userLoginInfo.get("loginPwd");
            //JDBC程式碼
            Connection conn = null;
            Statement stmt = null;
            ResultSet rs = null;


            try {
                //1.註冊驅動(作用:告訴java程式,即將要連線哪個品牌的資料庫)
                Class.forName("com.mysql.jdbc.Driver");
                //2.獲取連線(表示JVM的程式和資料庫程式之間的  通道 開啟了,使用完之後  必須關閉)
                String url = "jdbc:mysql://localhost:3306/bjpowernode"; //自己的地址連結
                String user = "root";
                String password = "333";
                conn = DriverManager.getConnection(url,user,password);
                //3.獲取資料庫操作物件(專門執行sql語句的物件)
                stmt = conn.createStatement();
                //4.執行sql語句(主要執行DQL、DML……)
                String sql = " select * from t_user where loginName ='"+ loginName +"'and loginPwd ='"+ loginPwd +"'";
                System.out.println(sql);

                //5.處理查詢結果集(只有當第4步執行的是select語句時,才有這第5步)
                rs = stmt.executeQuery(sql); //專門執行DQL查詢語句

                if(rs.next()){
                    flag = true;
                }

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //6.釋放資源(使用完資源後一定要關閉資源,java和資料庫屬於程式間通訊,開啟後一定要關閉)
                try{
                    if(stmt != null)
                        stmt.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }

                try{
                    if(conn != null)
                        conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }

                try{
                    if(rs != null)
                        rs.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }

            return flag;

        }

        /**
         * 初始化使用者介面
         * @return 使用者輸入使用者名稱、密碼等資訊
         */
        private static Map<String, String> inintUI () {
            Scanner s = new Scanner(System.in);

            System.out.print("使用者名稱:");
            String loginName = s.nextLine();
            System.out.print("密碼:");
            String loginPwd = s.nextLine();

            Map<String, String> userLoginInfo = new HashMap<>();

            userLoginInfo.put("loginName",loginName);
            userLoginInfo.put("loginPwd",loginPwd);

            return userLoginInfo;

        }

}

2.SQL隱碼攻擊的解決——PreparedStatement

2.1.改動1——Statement改為PreparedStatement
  •   //        Statement stmt = null;
      PreparedStatement stmt = null;
    
2.2.改動:2——改為佔位符—?
  •   //String sql = " select * from t_user where loginName ='"+ loginName +"'and loginPwd ='"+ loginPwd +"'";
      //一個問號表示一個佔位符
      String sql = " select * from t_user where loginName = ? and loginPwd = ? ";
    
2.3.改動:3——prepareStatement進行預編譯
  •   // stmt = conn.createStatement();
      //程式執行到此,會傳送sql語句”框子“給DBMS,然後它進行‘預編譯
      stmt = conn.prepareStatement(sql);
    
2.4.改動:4—— 給佔位符傳值
  •   stmt.setString(1,loginName);
      stmt.setString(2,loginPwd);
    
public class JDBCTestSqlInjectSolve {
    public static void main(String[] args) {
        //1.輸入資訊
        Map<String, String> userLoginInfo = inintUI();

        //2.驗證資訊是否正確
        boolean loginSuccess = login(userLoginInfo);
        System.out.println(loginSuccess ? "登陸成功" : "登陸失敗");

    }
    /**
     * 驗證使用者名稱,密碼是否正確
     * @param userLoginInfo
     * @return
     */
    private static boolean login (Map < String, String > userLoginInfo){
        boolean flag=false;
        String  loginName = userLoginInfo.get("loginName");
        String  loginPwd = userLoginInfo.get("loginPwd");
        //JDBC程式碼
        Connection conn = null;
//改動1:
//        Statement stmt = null;
        PreparedStatement stmt = null;

        ResultSet rs = null;


        try {
            //1.註冊驅動(作用:告訴java程式,即將要連線哪個品牌的資料庫)
            Class.forName("com.mysql.jdbc.Driver");
            //2.獲取連線(表示JVM的程式和資料庫程式之間的  通道 開啟了,使用完之後  必須關閉)
            String url = "jdbc:mysql://localhost:3306/bjpowernode"; //自己的地址連結
            String user = "root";
            String password = "333";
            conn = DriverManager.getConnection(url,user,password);
            //3.獲取資料庫操作物件(專門執行sql語句的物件)

//改動:2
//            String sql = " select * from t_user where loginName ='"+ loginName +"'and loginPwd ='"+ loginPwd +"'";
            //一個問號表示一個佔位符
            String sql = " select * from t_user where loginName = ? and loginPwd = ? ";
//改動:3
//            stmt = conn.createStatement();
            //程式執行到此,會傳送sql語句”框子“給DBMS,然後它進行‘預編譯
            stmt = conn.prepareStatement(sql);
//改動:4      給佔位符傳值
            stmt.setString(1,loginName);
            stmt.setString(2,loginPwd);


            //4.執行sql語句(主要執行DQL、DML……)
            rs = stmt.executeQuery(); //專門執行DQL查詢語句
            //5.處理查詢結果集(只有當第4步執行的是select語句時,才有這第5步)


            if(rs.next()){
                flag = true;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6.釋放資源(使用完資源後一定要關閉資源,java和資料庫屬於程式間通訊,開啟後一定要關閉)
            try{
                if(stmt != null)
                    stmt.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

            try{
                if(conn != null)
                    conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

            try{
                if(rs != null)
                    rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

        return flag;

    }

    /**
     * 初始化使用者介面
     * @return 使用者輸入使用者名稱、密碼等資訊
     */
    private static Map<String, String> inintUI () {
        Scanner s = new Scanner(System.in);

        System.out.print("使用者名稱:");
        String loginName = s.nextLine();
        System.out.print("密碼:");
        String loginPwd = s.nextLine();

        Map<String, String> userLoginInfo = new HashMap<>();

        userLoginInfo.put("loginName",loginName);
        userLoginInfo.put("loginPwd",loginPwd);

        return userLoginInfo;

    }

}

3.對比Statement 與 PreparedStatement

3.1.區別:
  • statement存在SQL隱碼攻擊問題,PreparedStatement解決了SQL隱碼攻擊問題

  • Statement是編譯一次執行一次,PreparedStatement是編譯一次,可執行N次,效率較高

  • PreparedStatement會在編譯階段做型別的安全檢查

  • 綜上:99%的情況使用PreparedStatement,1%的情況使用Statement

3.2.什麼情況使用Statement?
  • 業務要求必須進行SQL隱碼攻擊時
  • Statement支援注入,凡是要求需要進行sql語句拼接的,必須使用Statement
3.2.1. 比如::::把emp表內的資料按工資
  • desc排列
  • asc 排列

Statement實現:
在這裡插入圖片描述
在這裡插入圖片描述

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

            //JDBC程式碼
            Connection conn = null;
//改動1:
            Statement stmt = null;
//            PreparedStatement stmt = null;

            ResultSet rs = null;



            //排列方式:
            Scanner s = new Scanner(System.in);
            System.out.print("desc or asc ?:");
            String order = s.nextLine();

            try{

                //1.註冊驅動(作用:告訴java程式,即將要連線哪個品牌的資料庫)
                Class.forName("com.mysql.jdbc.Driver");
                //2.獲取連線(表示JVM的程式和資料庫程式之間的  通道 開啟了,使用完之後  必須關閉)
                String url = "jdbc:mysql://localhost:3306/bjpowernode"; //自己的地址連結
                String user = "root";
                String password = "333";
                conn = DriverManager.getConnection(url,user,password);
                //3.獲取資料庫操作物件(專門執行sql語句的物件)
                stmt = conn.createStatement();

                //4.執行sql語句(主要執行DQL、DML……)
                String sql = " select * from emp order by sal "+order;


                //5.處理查詢結果集(只有當第4步執行的是select語句時,才有這第5步)
                rs = stmt.executeQuery(sql); //專門執行DQL查詢語句
                boolean flag = rs.next();

                while (flag){
                    //列表標籤
                    String empno = rs.getString("empno");
                    String ename = rs.getString("ename");
                    String sal = rs.getString("sal");

                    System.out.println(empno+","+ename+","+sal);
                    flag = rs.next();
                }


            }catch (Exception e){
                e.printStackTrace();
            }finally { //為保證一定關閉,放在finally關閉
                //6.釋放資源(使用完資源後一定要關閉資源,java和資料庫屬於程式間通訊,開啟後一定要關閉)
                //按照從小到大的順序關閉
                try{
                    if(stmt != null)
                        stmt.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }

                try{
                    if(conn != null)
                        conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }

                try{
                    if(rs != null)
                        rs.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }


            }

        }

    }

preparedStatement 無法實現需求:
在這裡插入圖片描述

/***
 * 把emp表內的資料按工資
 *  *              desc排列
 *  *              asc 排列
 *      preparedStatement 無法實現需求
 */
class JDBCpreparedStatementOnly {
    public static void main(String[] args) {

        //JDBC程式碼
        Connection conn = null;
//改動1:

        PreparedStatement stmt = null;

        ResultSet rs = null;



        //排列方式:
        Scanner s = new Scanner(System.in);
        System.out.print("desc or asc ?:");
        String order = s.nextLine();

        try{

            //1.註冊驅動(作用:告訴java程式,即將要連線哪個品牌的資料庫)
            Class.forName("com.mysql.jdbc.Driver");
            //2.獲取連線(表示JVM的程式和資料庫程式之間的  通道 開啟了,使用完之後  必須關閉)
            String url = "jdbc:mysql://localhost:3306/bjpowernode"; //自己的地址連結
            String user = "root";
            String password = "333";
            conn = DriverManager.getConnection(url,user,password);
            //3.獲取資料庫操作物件(專門執行sql語句的物件)
            String sql = " select * from emp order by sal  ? ";
            stmt = conn.prepareStatement(sql);
            //改動:4      給佔位符傳值
            stmt.setString(1,order);


            //4.執行sql語句(主要執行DQL、DML……)


            //5.處理查詢結果集(只有當第4步執行的是select語句時,才有這第5步)
            rs = stmt.executeQuery();

            boolean flag = rs.next();

            while (flag){
                //列表標籤
                String empno = rs.getString("empno");
                String ename = rs.getString("ename");
                String sal = rs.getString("sal");

                System.out.println(empno+","+ename+","+sal);
                flag = rs.next();
            }


        }catch (Exception e){
            e.printStackTrace();
        }finally { //為保證一定關閉,放在finally關閉
            //6.釋放資源(使用完資源後一定要關閉資源,java和資料庫屬於程式間通訊,開啟後一定要關閉)
            //按照從小到大的順序關閉
            try{
                if(stmt != null)
                    stmt.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

            try{
                if(conn != null)
                    conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

            try{
                if(rs != null)
                    rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }


        }

    }

}

3.3.為什麼有些情況Statement可以,PreparedStatement不行????
  • 以3.2為例:把emp表內的資料按工資升序、降序排列
  • 正常sql語句應該是:
  •    select empno ,ename,sal from emp order by sal desc;//降序
    
  •    select empno ,ename,sal from emp order by sal asc; //升序
    

在這裡插入圖片描述

  • Statement程式
  •   //4.執行sql語句(主要執行DQL、DML……)
     String sql = " select * from emp order by sal "+order;
     System.out.println(sql);
    

在這裡插入圖片描述

  • PreparedStatement程式
  •   //3.獲取資料庫操作物件(專門執行sql語句的物件)
          String sql = " select * from emp order by sal  ? ";
      //預編譯
          stmt = conn.prepareStatement(sql);
      //給佔位符傳值
          stmt.setString(1,order);
          System.out.println(sql);
    

在這裡插入圖片描述

4.PreparedStatement的CRUD

4.1.增insert:向表中插入資料——(no=14,name=Bob,class_id=2020)
  •   	 //增  //insert into t_stu(no,name,class_id) values (14,'Bob',2020);
          String sql = "insert into t_stu(no,name, class_id ) values (?,?,?)";
          ps = conn.prepareStatement(sql);
          //      給佔位符傳值
          ps.setInt(1,14);
          ps.setString(2,"Bob");
          ps.setInt(3,2020);
          ps.executeUpdate();
    
4.2.刪delete:刪除no>20的全部資料
  •   		//刪     delete from t_stu  where no>20;
          String sql3 = "delete from t_stu  where no>?";
          ps = conn.prepareStatement(sql3);
          //      給佔位符傳值
          ps.setInt(1,20);
          ps.executeUpdate();
    
4.3.改update:no=20的資料,name都改為Anna
  •   //      //改 update t_stu set name='Anna' where no = 20 ;
          String sql2 = "update t_stu set name= ? where no=?";
          ps = conn.prepareStatement(sql2);
         // 給佔位符傳值
          ps.setString(1,"Anna");
          ps.setInt(2,20);
          ps.executeUpdate();
    
4.4.查retrieve:查詢no>10的所有資料
  •   //查	select * from t_stu where no>10;
          String sql4 = " select * from t_stu where no>?";
          ps = conn.prepareStatement(sql4);
      // 給佔位符傳值
          ps.setInt(1,10);
         
      //4.執行sql語句(主要執行DQL、DML……)
          rs = ps.executeQuery(); //專門執行DQL查詢語句
    
/**
 * 演示preparedStatement 的 CRUD
 */


import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.*;

public class preparedStatement {
    public static void main(String[] args) {
        //JDBC程式碼
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
        //1.註冊驅動(作用:告訴java程式,即將要連線哪個品牌的資料庫)
            Class.forName("com.mysql.jdbc.Driver");
        //2.獲取連線(表示JVM的程式和資料庫程式之間的  通道 開啟了,使用完之後  必須關閉)
            String url = "jdbc:mysql://localhost:3306/bjpowernode"; //自己的地址連結
            String user = "root";
            String password = "333";
            conn = DriverManager.getConnection(url,user,password);
        //3.獲取資料庫操作物件(專門執行sql語句的物件)
            //一個問號表示一個佔位符


        //增     insert into t_stu(no,name, class_id ) values (?,?,?)
            String sql = "insert into t_stu(no,name, class_id ) values (?,?,?)";
            //insert into t_stu(no,name,class_id) values (20,'James',2020);
            ps = conn.prepareStatement(sql);
            //      給佔位符傳值
            ps.setInt(1,14);
            ps.setString(2,"Bob");
            ps.setInt(3,2020);
            ps.executeUpdate();



//        刪     delete from t_stu  where no=30;
            String sql3 = "delete from t_stu  where no>?";
            ps = conn.prepareStatement(sql3);
            //      給佔位符傳值
            ps.setInt(1,20);
            ps.executeUpdate();


//      //改 update t_stu set name='dior' where no > ?;
            String sql2 = "update t_stu set name= ? where no=25?";
            ps = conn.prepareStatement(sql2);
//          給佔位符傳值
            ps.setString(1,"Anna");
            ps.setInt(2,20);
            ps.executeUpdate();



//查
            String sql4 = " select * from t_stu where no>?";
            ps = conn.prepareStatement(sql4);
//          給佔位符傳值
            ps.setInt(1,10);
            //程式執行到此,會傳送sql語句”框子“給DBMS,然後它進行‘預編譯


        //4.執行sql語句(主要執行DQL、DML……)
            rs = ps.executeQuery(); //專門執行DQL查詢語句
        //5.處理查詢結果集(只有當第4步執行的是select語句時,才有這第5步)
            boolean flag = false;
            flag = rs.next();
            while (flag){
                //列表標籤
                String no = rs.getString("no");
                String name = rs.getString("name");
                String class_id = rs.getString("class_id");

                System.out.println(no+","+name+","+class_id);
                flag = rs.next();
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //6.釋放資源(使用完資源後一定要關閉資源,java和資料庫屬於程式間通訊,開啟後一定要關閉)

            try{
                if(ps != null)
                    ps.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

            try{
                if(conn != null)
                    conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

            try{
                if(rs != null)
                    rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

相關文章