什么是SQL注入?基于实例讲解

一、SQL注入的含义:

SQL注入是一种常见的网络攻击,由于程序对输入数据的判断或者检验不严格,导致攻击者查询到了授权范围之外的数据,甚至还可以修改数据库中的数据,对数据库执行一些管理操作等,所以它的危害性也是比较大的。

像下面这个SQL,程序的本意是查询id=1的用户信息,但如果输入的参数后面又添加了OR 1=1,这样就会把所有的用户信息搜出来,显然,这是我们需要避免的:

SELECT * FROM t_user WHERE id = 1 OR 1=1;

二、实例演示:

首先创建一个测试表,并插入简单的数据(MySQL数据库):

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(30) DEFAULT NULL,
  `password` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO `t_user` VALUES ('1', 'John', '111111');
INSERT INTO `t_user` VALUES ('2', 'Tom', '222222');

接下来是Java程序部分,采用Spring Boot加上MyBatis的方式,如果对于搭建Spring Cloud工程还不太熟悉,可以参考之前的文章:手把手:Spring Cloud Alibaba项目搭建

在pom.xml文件中添加依赖:

<!-- 连接Spring Boot和MyBatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

<!-- mysql 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

然后是Java各层的代码,从controller一直到dao,为了完整起见,我把代码都贴出来。controller层有一个方法,参数为用户名,返回该用户的详细信息,为了演示方便,此处返回一个列表:

package com.fullstack.commerce.user.controller;

import com.fullstack.commerce.user.entity.User;
import com.fullstack.commerce.user.service.UserService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("user")
public class UserController {
    @Resource
    private UserService userService;

    @RequestMapping("getUserInfo")
    @ResponseBody
    // 根据用户姓名查询出用户列表信息
    public List<User> getUserInfo(@RequestParam("username")String username){
        List<User> result =  userService.getUserInfo(username);
        return result;
    }
}

service层比较简单,有一个接口和对应的实现类,而实现类就是调用dao的方法,把用户列表查询出来:

package com.fullstack.commerce.user.service;

import com.fullstack.commerce.user.entity.User;
import java.util.List;

public interface UserService {
    List<User> getUserInfo(String username);
}
package com.fullstack.commerce.user.service.impl;

import com.fullstack.commerce.user.dao.UserDao;
import com.fullstack.commerce.user.entity.User;
import com.fullstack.commerce.user.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserDao userDao;
    @Override
    public List<User> getUserInfo(String username) {
        return userDao.getUserInfo(username);
    }
}

dao层就是一个接口,里面只有一个方法:

package com.fullstack.commerce.user.dao;

import com.fullstack.commerce.user.entity.User;
import java.util.List;

public interface UserDao {
    List<User> getUserInfo(String username);
}

除了上面的业务代码,还需要有一个启动类:

package com.fullstack.commerce.user;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan(basePackages = "com.fullstack.commerce.user.dao")
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

以下是对应的mapper文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fullstack.commerce.user.dao.UserDao">
    <resultMap id="UserMap" type="com.fullstack.commerce.user.entity.User">
        <result property="id" column="id" jdbcType="INTEGER"/>
        <result property="username" column="username" jdbcType="VARCHAR"/>
        <result property="password" column="password" jdbcType="VARCHAR"/>
    </resultMap>

    <select id="getUserInfo" resultMap="UserMap">
        SELECT * FROM t_user WHERE username = ${username}
    </select>
</mapper>

注意,在上面这个mapper文件中,只有一个select语句,而这个语句传参使用了符号$,它会把参数值直接进行替换,而不会进行预编译(如果使用占位符#,就会进行预编译,从而可以防止SQL注入)。

当然,application.yml文件也需要配置一下,数据库用户名和密码替换成实际的:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: myuser 
    password: myuser
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: mapper/*.xml

代码就算写完了,接下来我们测试一下看看。

三、测试验证:

启动程序,我们来进行测试,在浏览器里面输入以下地址,然后回车:

http://localhost:8080/user/getUserInfo?username='John'

没问题,我们是要查找用户名为John的数据,它返回了如下的结果,正是我们所期望的:

图片

但如果我们把参数重新设置一下,变成如下这样:

http://localhost:8080/user/getUserInfo?username='John' OR 1=1

那就相当于在数据库中执行下面的SQL了,也就是返回所有的用户数据:

SELECT * FROM t_user WHERE username = 'John' OR 1=1

看结果,确实是这样,把表中的两条记录都返回到了客户端(测试数据只插了两条记录,如果有多条,同样会把所有的数据都返回过来):

图片

这显然是不对的,接口把查询范围之外的数据都搜索出来了,如果还有一些修改数据或者操纵数据库的一些命令,那就更危险了。

当然,我们把mapper文件里面的参数部分改成占位符#,这样就会先进行预编译,就不会发生刚才的情况了。修改成#以后运行程序,再执行上面的url,就会返回空了,因为这个时候是去数据库中查询用户名为【'John' OR 1=1】的记录,显然是不存在的。

四、总结:

SQL注入是比较常见的一种攻击方式,如果ORM框架选择了MyBatis,处理这种问题的方式也相对比较简单,就是对于参数的处理要使用占位符,而不是直接把用户的输入进行简单的拼接。

本文主要是介绍SQL注入的基本概念,并用实际的例子来进行演示,以便让朋友们有一个直观的入门认识。在实际情况中,SQL注入细分也有很多的类型,而完善的处理方式也包括controller层的输入检验等,甚至api网关或者反向代理也需要考虑这样的情况,此处不再深入。

鸣谢:

https://www.geeksforgeeks.org/sql-injection/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辣香牛肉面

感谢有缘之人的馈赠

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值