1、什么是注入攻击

  使用了用户输入的但是我们没有校验过的数据,来拼装一个可以行的指令,交给系统去执行,结果导致执行了我们不希望发生的命令。注入攻击用很多种,最常见的是SQL注入。

2、SQL注入攻击

  Java程序员知道,使用Statement进行查询时会造成SQL注入攻击,从而使用PreparedStatement来进行SQL预编译,从而有效的防止SQL注入攻击。但是日常开发中,我们一般多使用框架来进行数据库操作,如JdbcTemplate、Spring-Data-Jpa、Mybatis等,但是使用起来,我们也要注意,避免写出可注入程序。

3、JdbcTemplate注入代码示例

  3.1、UserDO实体类与UserDTO

/**
* @author caofanqi
* @date 2020/1/20 13:08
*/
@Data
@Entity
@Table(name = "user")
public class UserDO {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String name;


public UserDTO buildUserDTO(){
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(this,userDTO);
return userDTO;
}

}


/**
* @author caofanqi
* @date 2020/1/20 13:08
*/
@Data
public class UserDTO {


private Long id;

private String name;

}


  3.2、UserController,提供一个根据用户名称进行查询用户的API接口

/**
* 用户控制层
*
* @author caofanqi
* @date 2020/1/20 13:05
*/
@RestController
@RequestMapping("/users")
public class UserController {

@Resource
private UserService userService;


@GetMapping
public List<UserDTO> query(String name) {
return userService.query(name);
}


}


  3.3、UserService实现类,使用JdbcTemplate进行条件拼接查询,会产生SQL注入问题

/**
* 用户业务层实现类
*
* @author caofanqi
* @date 2020/1/20 13:52
*/
@Slf4j
@Service
public class UserServiceImpl implements UserService {


@Resource
private JdbcTemplate jdbcTemplate;


@Override
public List<UserDTO> query(String name) {

String sql = "SELECT * FROM user WHERE name = '" + name + "'";
log.info("执行的SQL为:{}",sql);
List<UserDO> queryResult = jdbcTemplate.query(sql, BeanPropertyRowMapper.newInstance(UserDO.class));

List<UserDTO> result = queryResult.stream().map(UserDO::buildUserDTO).collect(Collectors.toList());

return result;
}

}


  3.4、启动项目,向数据库中插入3条数据,如下:

  API安全(二)-SQL注入与防范_sql

  3.5、访问http://127.0.0.1:9090/users?name=zhangsan 可以正常查询到结果

  API安全(二)-SQL注入与防范_数据_02

  3.6、但是执行http://127.0.0.1:9090/users?name=' or '1'='1 时,就会把数据库中所有的用户都查询出来,这样就会导致信息泄漏。

  API安全(二)-SQL注入与防范_sql注入_03

  控制台打印日志如下

2020-01-20 21:48:07.263  INFO 18540 --- [nio-9090-exec-3] c.c.s.service.impl.UserServiceImpl       : 执行的SQL为:SELECT * FROM user WHERE name = '' or '1'='1'


  往下追溯源码可以发现,该query方法,底层使用的是Statement

  API安全(二)-SQL注入与防范_sql注入_04

  3.7、解决这个问题,我们修改query代码如下,使用了预编译SQL。

@Override
public List<UserDTO> query(String name) {

String sql = "SELECT * FROM user WHERE name = ? ";
List<UserDO> queryResult = jdbcTemplate.query(sql, new Object[]{name}, BeanPropertyRowMapper.newInstance(UserDO.class));
List<UserDTO> result = queryResult.stream().map(UserDO::buildUserDTO).collect(Collectors.toList());

return result;
}


  3.8、http://127.0.0.1:9090/users?name=' or '1'='1 时,查询不到结果,预防了SQL注入攻击

  API安全(二)-SQL注入与防范_sql注入_05

   往下追溯源码可以发现,该query方法,底层使用的是PreparedStatement

  API安全(二)-SQL注入与防范_数据_06

4、EntityManager注入代码示例

  在使用JPA的EntityManager执行拼接SQL时,将参数拼接到SQL中,一样会有SQL注入问题

@Slf4j
@Service
public class UserServiceImpl implements UserService {

@PersistenceContext
private EntityManager entityManager;

@Override
public List<UserDTO> query(String name) {

String sql = "SELECT * FROM user WHERE name = '" + name + "'";

Query nativeQuery = entityManager.createNativeQuery(sql, UserDO.class);
List<UserDO> queryResult = nativeQuery.getResultList();

List<UserDTO> result = queryResult.stream().map(UserDO::buildUserDTO).collect(Collectors.toList());

return result;
}

}


  需要修改成如下:

String sql = "SELECT * FROM user WHERE name = ? ";
Query nativeQuery = entityManager.createNativeQuery(sql, UserDO.class);
nativeQuery.setParameter(1, name);
List<UserDO> queryResult = nativeQuery.getResultList();


5、如何防止SQL注入

  上面的情况只是针对与查询,多查出来数据,更危险的攻击可能会修改或删除数据,甚至调用存储过程,删表等。我们如何防止SQL注入呢?

  5.1、控制数据库用户权限(对分配给应用程序的用户权限进行限定,一般只有对数据的CRUD权限)

  5.2、对传入的参数进行校验

  5.3、使用一些高级的框架进行数据库操作,对于一些复杂的情况,需要进行SQL拼接时,不要将参数拼接到SQL中,要使用预编译SQL。


项目源码:​​https://github.com/caofanqi/study-security/tree/dev-SQL-injection​