1、什么是注入攻擊
使用了用戶輸入的但是我們沒有校驗過的數據,來拼裝一個可以行的指令,交給系統去執行,結果導致執行了我們不希望發生的命令。注入攻擊用很多種,最常見的是SQL注入。
2、SQL注入攻擊
Java程序員知道,使用Statement進行查詢時會造成SQL注入攻擊,從而使用PreparedStatement來進行SQL預編譯,從而有效的防止SQL注入攻擊。但是日常開發中,我們一般多使用框架來進行數據庫操作,如JdbcTemplate、Spring-Data-Jpa、Mybatis等,但是使用起來,我們也要注意,避免寫出可注入程序。
JdbcTemplate注入代碼示例
/** * @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
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();
如何防止SQL注入
上面的情況只是針對與查詢,多查出來數據,更危險的攻擊可能會修改或刪除數據,甚至調用存儲過程,刪表等。我們如何防止SQL注入呢?
5.1、控制數據庫用戶權限(對分配給應用程序的用戶權限進行限定,一般只有對數據的CRUD權限)
5.2、對傳入的參數進行校驗
5.3、使用一些高級的框架進行數據庫操作,對於一些復雜的情況,需要進行SQL拼接時,不要將參數拼接到SQL中,要使用預編譯SQL。
項目源碼:https://github.com/caofanqi/study-security/tree/dev-SQL-injection