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条数据,如下:
3.5、访问http://127.0.0.1:9090/users?name=zhangsan 可以正常查询到结果
3.6、但是执行http://127.0.0.1:9090/users?name=‘ or ‘1‘=‘1 时,就会把数据库中所有的用户都查询出来,这样就会导致信息泄漏。
控制台打印日志如下
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
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注入攻击
往下追溯源码可以发现,该query方法,底层使用的是PreparedStatement
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