我掌握的新兴技术-防SQL注入及实现方案原理

2024-02-06 10:09:55 浏览数 (1)

什么是SQL注入?

SQL注入是一种常见的网络安全漏洞,它允许攻击者通过在应用程序中插入恶意SQL代码来执行非法操作,如获取敏感数据、修改数据库内容或删除数据等。SQL注入攻击通常发生在应用程序与数据库之间的交互过程中,攻击者利用应用程序对用户输入的不安全处理,将恶意SQL代码注入到SQL查询中,从而实现攻击目的。

SQL注入攻击的核心原理是利用应用程序与数据库之间的交互过程中,用户输入的数据没有经过严格的验证和过滤,从而将恶意SQL代码注入到SQL查询中。攻击者可以通过这种方式执行任意的SQL查询,从而实现对数据库的非法操作。比如,直接在用户登录的时候,恶意者在密码框输入:‘1111 or ‘1’=‘1’,那么后面语句or ‘1’=‘1’ 永远都为true,就会查找所有数据。

SQL注入的风险

可想而知,SQL注入攻击,对程序或者数据都会一定风险,如果恶意者使用的是delete或者drop其他严重SQL代码注入,带来的影响是不可估量的,具体风险包括如下:

  1. 数据泄露:攻击者可以通过SQL注入攻击获取数据库中的敏感数据,如用户密码、银行账户等。
  2. 数据篡改:攻击者可以通过SQL注入攻击修改数据库中的数据,如修改用户权限、删除重要数据等。
  3. 系统权限提升:攻击者可以通过SQL注入攻击获取数据库的管理员权限,从而实现对整个数据库的完全控制。
  4. 服务中断:攻击者可以通过SQL注入攻击破坏数据库服务,导致应用程序无法正常运行。

SQL注入的防范措施

那么SQL注入对我们系统影响这么大,应该如何去防范呢?其实,实际项目开发中,使用ORM框架,已经对这一块进行了优化,或者JDBC数据库连接也是使用参数预编译的方式,防止SQL注入攻击,总的来说,有以下措施:

  1. 参数化查询:使用参数化查询可以避免将用户输入的数据直接拼接到SQL查询中,从而防止恶意SQL代码的注入。
  2. 输入验证:对用户输入的数据进行严格的验证和过滤,避免包含恶意SQL代码的数据进入SQL查询。
  3. 最小权限原则:为应用程序分配最小的数据库权限,避免攻击者通过SQL注入攻击获取更高的权限。
  4. 定期审计:定期对应用程序和数据库进行安全审计,发现并修复潜在的安全漏洞。

参数拼接模拟SQL注入

接下来,模拟SQL注入场景,只有知道如何出现问题,才能从问题上触发解决SQL注入。有如下代码,主要是根据用户名查询用户信息,采用的是将用户名参数拼接到SQL上,这就很容易出现SQL注入问题。

代码语言:java复制
   @Override
    public List<User> findUser(String name) {
        List<User> myUserList= new ArrayList<>();
        String sql="select * from tbuser where username ='" name "'";
        Map<String, Object> param = new HashMap<>();
        List<Map<String, Object>> mapList=new ArrayList<>();
        mapList=jdbcTemplate.queryForList(sql,param);
        for(int i=0;i<mapList.size();i  ){
            Map<String,Object> testmap= mapList.get(i);
            User myuser=new User();
            myuser.setName((String) testmap.get("username"));
            myUserList.add(myuser);
        }
        return myUserList;
    }

假设请求参数,name传:

代码语言:sql复制
1111' OR '1' = '1

那么sql拼接之后就不会变成:

代码语言:sql复制
select * from tbuser where username ='1111' OR '1' = '1'

最终结果,虽有uname不满足,但是后面or条件 1=1为true就会导致查询全部数据,最终造成数据泄露。

参数预编译防止SQL注入

参数拼接组装SQL去查询,会出现SQL注入问题,所以在实际开发中,要避免这种情况,可以把上述代码改成,参数映射的方式,也就是预编译。如下代码:

代码语言:java复制

    @Override
    public List<User> findUser(String name) {
        List<User> myUserList= new ArrayList<>();
        String sql="select * from tbuser where username =:name";
        Map<String, Object> param = new HashMap<>();
        param.put("name",name);
        List<Map<String, Object>> mapList=new ArrayList<>();
        mapList=jdbcTemplate.queryForList(sql,param);
        for(int i=0;i<mapList.size();i  ){
            Map<String,Object> testmap= mapList.get(i);
            User myuser=new User();
            myuser.setName((String) testmap.get("username"));
            myUserList.add(myuser);
        }
        return myUserList;
    }

这样的话,即使请求参数name为 1111' OR '1' = '1,也会把整个参数赋值给name,这里其实会分成两部,预编译SQL的过程并不直接生成一个新的字符串形式的SQL语句,而是数据库系统将SQL模板与参数分开处理。

代码语言:javascript复制
String sql = "SELECT * FROM tbuser WHERE username = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "1111' OR '1' = '1");

最终组装的SQL语句就是:

代码语言:sql复制
select * from tbuser where username = '1111'' OR ''1'' = ''1'

可以看到,在内部单引号加多了一个转义单引号,就会把请求参数当做一整个字符串进行查询。这样就不会真正查询出数据。

使用功能ORM框架(mybatis-plus)

上面讲解了,使用SQL参数预编译,降低SQL注入风险,其实,在实际开发中,使用功能ORM框架,比如mybatis-plus就已经整合了上述的方式,并且做了参数和SQL语句分开,底层会对参数进行过滤解析。

代码语言:javascript复制
// 创建一个 QueryWrapper 对象
QueryWrapper<User> queryWrapper = new QueryWrapper<>();

// 设置查询条件
queryWrapper.eq("username", "test_user")
           .eq("password", "test_password");

// 使用 QueryWrapper 进行查询
List<User> users = userMapper.selectList(queryWrapper);

上述代码是创建了一个 QueryWrapper 对象,然后使用 eq() 方法设置查询条件。MyBatis-Plus 会自动处理 SQL 注入风险,并将查询条件预编译为一个预编译对象。最后,我们使用 userMapper.selectList() 方法执行查询。并且,MyBatis-Plus 会自动处理 SQL 注入风险,因此在使用 QueryWrapper 时,无需担心 SQL 注入的问题。

当这个查询被执行时,MyBatis会创建一个PreparedStatement对象,并通过调用其setString()等方法来设置实际的参数值。这种方式由JDBC驱动程序内部实现,它会根据SQL类型对参数值进行适当的转义,从而有效地阻止了SQL注入攻击,因为用户输入的数据不再能够被解析为SQL命令的一部分。

PreparedStatement 防SQL注入原理

总的来说,防SQL注入最终底层还是使用功能JDBC的预处理对象PreparedStatement。PreparedStatement阻止SQL注入的实现原理基于预编译SQL语句和参数化查询。以下是具体的步骤:

  1. 预编译SQL语句: 当使用PreparedStatement时,首先会将包含占位符(通常用问号?表示)的SQL语句发送给数据库服务器进行编译。这个过程独立于实际的参数值,它创建了一个执行计划模板。
  2. 参数绑定: 随后,在应用程序中设置参数时,并不是直接将参数拼接到已编译的SQL字符串中,而是通过调用PreparedStatement对象的方法(如setString(), setInt()等)将每个参数与对应的占位符关联起来。这些方法确保参数被正确地类型转换并进行转义处理。
  3. 转义处理: 在参数被传递到数据库之前,JDBC驱动程序会根据参数类型自动对特殊字符进行转义,例如对于字符串参数,它会确保单引号(')被正确转义,使得恶意用户输入的单引号不会导致SQL语法错误或注入攻击。即使用户尝试输入带有SQL命令的字符串,由于其内容会被当作一个整体的数据值对待,而不是被执行的SQL代码,因此不会影响SQL语句的结构。
  4. 执行安全的SQL语句: 最终执行的SQL是预编译后的模板加上已经过安全处理的参数,这确保了无论用户输入如何变化,都不会影响SQL语句的整体逻辑,从而有效防止了SQL注入攻击。

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

0 人点赞