최근에 SQL 인젝션 공격이란?, 또 방어하는 방법에 대해서, 간단한 질문을 받은 적이 있습니다.
Q) SQL 인젝션 공격이란? 또 방어하기 위한 방법을 소개해주세요
A)
공격자
가 SQL 쿼리에 악의적인 코드를 넣어서, 데이터베이스를 조작/정보 탈취하는 공격 입니다.
JAVA 기준으로 ORM 기술을 사용하거나, 입력값을 검증하는 방법으로 회피할 수 있습니다.
select * from Test Where name = :name"
이런 쿼리가 있을때name=sdf'or '1'='1'
요런게 들어오면, name이 입력값으로 대체 되면서select * from Test Where name = name='sdf'or '1'='1'
요런 쿼리가 발송되어, 무조건 모든 정보를 반환하게 되는 경우가 생길 수도?@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserRepository userRepository;
@GetMapping("/search/test1")
public ResponseEntity<?> getUsersByNameAndAge1(
@RequestParam String name
) {
List<User> findData = userRepository.findByNameAndAgeJPQL(name);
return ResponseEntity.ok(findData);
}
@GetMapping("/search/test2")
public ResponseEntity<?> getUsersByNameAndAge2(
@RequestParam String name
) {
List<User> findData = userRepository.findByNameAndAgeJPQL2(name);
return ResponseEntity.ok(findData);
}
@GetMapping("/search/test3")
public ResponseEntity<?> getUsersByNameAndAge3(
@RequestParam String name
) {
List<User> findData = userRepository.findByNameAndAgeNativeQuery(name);
return ResponseEntity.ok(findData);
}
@GetMapping("/search/test4")
public ResponseEntity<?> getUsersByNameAndAge4(
@RequestParam String name
) {
List<User> findData = userRepository.findAllByName(name);
return ResponseEntity.ok(findData);
}
@GetMapping("/search/test5")
public ResponseEntity<?> getUsersByNameAndAge5(
@RequestParam String name
) {
List<User> findData = userRepository.findByNameAndAgeNativeQuery1(name);
return ResponseEntity.ok(findData);
}
}
GET /users/search/test번호?name=John' OR '1'='1'
SQL 인젝션
이 방어되는지 살펴보겠습니다.@Repository
public interface UserRepository extends JpaRepository<User, Long> {
//JPQL + 파라미터 바인딩 (위치기반)
@Query("SELECT u FROM User u WHERE u.name = ?1")
List<User> findByNameAndAgeJPQL(String name);
//JPQL + 파라미터 바인딩 (@Praram 기반 바인딩)
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findByNameAndAgeJPQL2(@Param("name") String name);
//Native Query + 파라미터 바인딩(@Param 기반 바인딩)
@Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true)
List<User> findByNameAndAgeNativeQuery(@Param("name") String name);
//네이밍 쿼리
List<User> findAllByName(String name);
//Native Query + 파라미터 바인딩 (위치기반)
@Query(value = "SELECT * FROM users WHERE name = ?1", nativeQuery = true)
List<User> findByNameAndAgeNativeQuery1(String name);
}
name | age |
---|---|
강성욱 | 28 |
백과 | 2 |
localhost:8080/users/search/test번호?name=John' OR '1'='1'
John
이 없기 떄문에, 데이터를 찾지 못해야 합니다.방법 | 공격 방어 성공? |
---|---|
JPQL + 파라미터 바인딩 (위치기반) | O |
JPQL + 파라미터 바인딩 (@Praram 기반 바인딩) | O |
Native Query + 파라미터 바인딩(@Param 기반 바인딩) | O |
네이밍 쿼리 | O |
Native Query + 파라미터 바인딩 (위치기반) | O |
p6spy
를 사용해서, 실제 나간 SQL을 로깅해보았습니다.//test1
select u1_0.id,u1_0.age,u1_0.name from users u1_0 where u1_0.name='John'' OR ''1''=''1''';
//test2
select u1_0.id,u1_0.age,u1_0.name from users u1_0 where u1_0.name='John'' OR ''1''=''1''';
//test3
SELECT * FROM users WHERE name = 'John'' OR ''1''=''1''';
//test4
select u1_0.id,u1_0.age,u1_0.name from users u1_0 where u1_0.name='John'' OR ''1''=''1''';
//test5
SELECT * FROM users WHERE name = 'John'' OR ''1''=''1''';
Where
절을 통해 찾는 값이 제가 생각한대로가 아닌, OR 같은 부분도 모두 문자열로 변경되었다는 점이었습니다.PreparedStatement
의 setObject
나, setParameter
같은 메서드를 사용해서 안전하게 바인딩 처리하고 있는 것 같습니다.setParameter
를 사용해서 안전하게 바인딩 하고 있었습니다.native query
나, jpql
에서는 일부 공격이 통할 것 같은 느낌이 들었습니다.Hibernate
는 강력하네요. 일딴 제가 생각한 모든 케이스는 방어가 잘 되었습니다.혹시, 방어 할수 없는 코드를 제시해주실 분은 꼭꼭 댓글 부탁드립니다..