浅谈sql注入问题

Posted Youryang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈sql注入问题相关的知识,希望对你有一定的参考价值。

浅谈sql注入问题

在学习java的jdbc时学到这么一个东西,我们有时候会忽略这个问题,我们摘出来做一下简单的讨论。

Sql注入问题 及statement和PreparedStatement的区别:

首先我们来复习一下jdbc操作数据库的几个步骤:

注册驱动、获取连接、获取数据库操作对象,执行sql语句、处理查询结果集、释放资源

那么之前我们一直用的是prestatement来获取数据库操作对象,那么大家知道prestatement其实是属于预编译的数据库操作对象。那么prestatement是不是全能的呢?并不是,比如京东上的商品价格展示,就要求升序或者降序排列,那么必须拼接asc和desc时,prestatement是否依然有效呢?

我们简单模拟一个升序降序排列功能

示例一:

package jdbc;

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

public class JdbcTest07sql1 
    public static void main(String[] args) 
        //用户控制台输入desc就是降序,输入asc就是升序
        Scanner s = new Scanner(System.in);
        System.out.println("请输入desc或asc,desc表示降序,asc表示升序");
        System.out.println("请输入:");
        String keyWords = s.nextLine();

        //执行SQL
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try 
            //注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/orcl","root","123456");
            //获取数据库操作对象
            String sql = "select ename from emp order by empno ? ";
            ps = conn.prepareStatement(sql);
            ps.setString(1,keyWords);
            //执行sql
            rs=ps.executeQuery();
            //遍历结果集
            while (rs.next())
                System.out.println(rs.getString("ename"));
            
         catch (ClassNotFoundException e) 
            e.printStackTrace();
         catch (SQLException throwables) 
            throwables.printStackTrace();
        finally 
            if(rs!=null)
                try 
                    rs.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();
                
            
        

    


此程序会报这个错误:

java.sql.SQLSyntaxErrorException: 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 ‘‘desc’’ at line 1

可以看到是由于sql语句给占位符传值时多加了一个引号。

那么此时必须用到statement,来拼接字符串,此问题就得到了解决。只展示和前面代码不同的地方

try 
            //注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/orcl","root","123456");
            //获取数据库操作对象
            stmt = conn.createStatement();
            //执行SQL
            String sql = "select ename from emp order by empno "+keyWords;
            rs = stmt.executeQuery(sql);
            //遍历结果集
            while (rs.next())
                System.out.println(rs.getString("ename"));
            

那么使用statement就一定好吗?并不是,在进行用户登录注册时又会出现怎样的问题呢?

在这里我们来模拟一个用户登陆注册功能

package jdbc;

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

public class JdbcTest06sql1 
    public static void main(String[] args) 
        //初始化一个界面
        Map<String,String> userLoginInfo = initUI();
        //验证用户名和密码
        boolean loginSuccess = login(userLoginInfo);
        //最后输出结果
        System.out.println(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;
    
    /*
    用户登录
    @param userLoginInfo 用户登录信息
    @return false失败 true成功
     */
    private static boolean login(Map<String, String> userLoginInfo) 
        //打标记
        boolean loginSuccess = false;
        //单独定义变量
        String loginName = userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get("loginPwd");
        //JBDC代码
        Connection conn = null;
        Statement stmt = null;//这里使用PreparedStatement预编译的数据库操作对象
        ResultSet rs = null;

        try 
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/orcl","root","123456");
            //3.获取的数据库操作对象
            stmt = conn.createStatement();

            //4.执行sql
            String sql = "select * from t_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 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;
    


比如我们这样来访问数据库中的数据

用户名:zhangsan

密码:zhangsan’ or ‘1’=’1

密码不正确但依然访问到了数据库的内容

那么这种现象就被称作是sql注入现象。

这种现象被称为sql注入(安全隐患)(黑客经常使用)

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

执行到rs = stmt.executeQuery(sql);时,会把sql语句发送给DBMS(数据库管理系统),会把sql语句进行编译,正好将用户提供的非法信息编译进去,导致了原sql语句含义被扭曲了

那么我们怎么解决sql注入问题呢?同样也只展示和前面代码不同的部分

//1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取连接
        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/orcl","root","123456");
        //3.获取预编译的数据库操作对象
        //SQL语句的框子。其中一个?,表示一个占位符,一个?将来接收一个”值“,注意:占位符不能使用单引号括起来。
        String sql = "select * from t_user where loginName = ? and loginPwd = ?";//?称为占位符
        //程序执行到此处,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译。
        ps = conn.prepareStatement(sql);
        //给占位符?传值(第一个问号下标为1,第二个问号下标为2,JDBC中所有下标从1开始。)
        ps.setString(1,loginName);
        ps.setString(2,loginPwd);
        //4.执行sql

        rs = ps.executeQuery();
        //5.处理结果集
        if(rs.next())
            loginSuccess = true;
        

只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,不起作用。要想用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement。现在来看一下PreparedStatement是什么?

PreparedStatement继承了java.sql.Statement,PreparedStatement属于预编译的数据库操作对象,PreparedStatement的原理是:预先对sql语句的框架进行编译,然后再给sql语句传"值";

写到这里,我们顺便来对比一下statement及prestatement的区别:

1.Statement存在sql注入问题,PreparedStatement解决了sql注入问题

2.Statement编译一次执行一次,PreparedStatement编译一次,可执行N次。PreparedStatement效率较高一些

3.PreparedStatement会在编译阶段做类型的安全检查。

综上所述PreparedStatement使用较多,只有极少数情况下需要使用Statement。

以上是关于浅谈sql注入问题的主要内容,如果未能解决你的问题,请参考以下文章

浅谈sql注入问题

浅谈C#.NET防止SQL注入式攻击

浅谈防SQL注入

sql注入浅谈sql注入中的Post注入

浅谈SQL注入

web安全 浅谈sql注入