JPA를 쓰면 SQL 인젝션 공격에 무조건적 방어가 될까? (JPA, JPQL, JDBC)

BaekGwa·2024년 12월 7일
0

Spring

목록 보기
9/9
post-thumbnail

최근에 SQL 인젝션 공격이란?, 또 방어하는 방법에 대해서, 간단한 질문을 받은 적이 있습니다.

Q) SQL 인젝션 공격이란? 또 방어하기 위한 방법을 소개해주세요

A) 공격자가 SQL 쿼리에 악의적인 코드를 넣어서, 데이터베이스를 조작/정보 탈취하는 공격 입니다.
JAVA 기준으로 ORM 기술을 사용하거나, 입력값을 검증하는 방법으로 회피할 수 있습니다.

혹시, 결론 보실분은 바로 맨밑으로 내리면 됩니다!


😕 정확한 답변이었을까?

  • 다시 곰곰히 생각해보니 아닐수도 있을 것 같다 라는 생각이 들었습니다.
  • native query나, JQPL을 사용할 때, Query Param으로 다음과 같은 조건을을 같이 넣어주면 안될 것 같다 라는 생각이 들었습니다.
    • select * from Test Where name = :name" 이런 쿼리가 있을때
    • Param으로 name=sdf'or '1'='1' 요런게 들어오면, name이 입력값으로 대체 되면서
    • select * from Test Where name = name='sdf'or '1'='1' 요런 쿼리가 발송되어, 무조건 모든 정보를 반환하게 되는 경우가 생길 수도?

시나리오

  • 익숙한, SpringBoot를 API 서버로서 사용, RestAPI 규정으로 이런 코드가 있습니다.
@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);
    }
}
  • 해당 API EndPoint로 다음과 같은 요청이 접수되는 시나리오로 가정 하겠습니다.
    • GET /users/search/test번호?name=John' OR '1'='1'
  • ORM 기술 중, 다양한 JPA 기술을 활용하여 SQL 인젝션이 방어되는지 살펴보겠습니다.

다양한 Query 작성

@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);
}
  • 다양한 방법을 사용해서 비슷한 기능을 하도록 구현하였습니다.
  • DB에는 2개의 데이터가 있으며, 다음과 같습니다.
nameage
강성욱28
백과2
  • 모든 api를 호출해서 결과가 어떻게 이뤄지는지 확인해 보겠습니다.

결과 확인

  • API 를 호출할때, 다음과 같은 경로로 요청을 진행합니다.
  • localhost:8080/users/search/test번호?name=John' OR '1'='1'
  • 결과는 DB에는 John 이 없기 떄문에, 데이터를 찾지 못해야 합니다.
방법공격 방어 성공?
JPQL + 파라미터 바인딩 (위치기반)O
JPQL + 파라미터 바인딩 (@Praram 기반 바인딩)O
Native Query + 파라미터 바인딩(@Param 기반 바인딩)O
네이밍 쿼리O
Native Query + 파라미터 바인딩 (위치기반)O
  • 생각과는 다르게, 모두 방어에 성공하였습니다.
  • 물론 저의 해킹 실력이 너무 나쁜것도 있겠지만, 결과적으로는 모두 방어가 되었습니다.

결과 분석 (Log)

  • 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 같은 부분도 모두 문자열로 변경되었다는 점이었습니다.
  • 아마 내부적으로, PreparedStatementsetObject나, setParameter 같은 메서드를 사용해서 안전하게 바인딩 처리하고 있는 것 같습니다.
  • 즉, SQL Injection 공격에 안전해진 것 이라고 생각합니다.
  • 역시, 대부분 setParameter를 사용해서 안전하게 바인딩 하고 있었습니다.
    • 의도하여 SQL Injection 이나, Binding의 안전함을 위해서 채택했다는 말은 없지만, 분명 초기 개발당시에 고려한 부분이지 않을까 싶습니다.
      Hibernate User Guide

결론

  • 처음 글을 쓸때는, 솔찍히, native query나, jpql에서는 일부 공격이 통할 것 같은 느낌이 들었습니다.
  • 하지만, Hibernate는 강력하네요. 일딴 제가 생각한 모든 케이스는 방어가 잘 되었습니다.

결론은, JPA를 사용하면, SQL 인젝션 공격이 방어가 됩니다.

혹시, 방어 할수 없는 코드를 제시해주실 분은 꼭꼭 댓글 부탁드립니다..

profile
현재 블로그 이전 중입니다. https://blog.baekgwa.site/

0개의 댓글