SQL injection

SQL injection 이란 보안상의 허점을 이용해 사용자가 정의한 SQL 문외 조작된 SQL 을 주입하고 실행시켜 데이터베이스에 저장된 중요한 정보를 가져오는 공격 기법을 의미한다.

SQL Injection 공격을 받은 여기 어때

Error Based SQL injection

논리적 에러를 이용한 SQL injection

  • 잘못된 SQL 을 이용해 고의로 에러를 발생시는 공격 기법이다.
  • 예외로 던저진 Message 를 통해 테이블 명과 컬럼과 같은 테이블 정보 를 얻어낼 수 있다.
  • SQL 구문 정보를 변경해 사용자 인증을 우회해 접속할 수 있다.

에러 메시지를 이용한 테이블 정보 확인

Union Based SQL injection

Union 명령을 이용한 SQL injection

  • 정상적으로 실행하는 쿼리와 정보를 탈취하기 위한 쿼리문을 Union 연산자를 통해 실행시킨다.
  • 데이터 형식과 컬럼 수가 일치하면 Union 연산자를 이용해 데이터를 가져올 수 있다.
SELECT uid FROM user_table WHERE uid='' UNION SELECT upw FROM user_table WHERE uid='admin' -- and...

참고

https://www.baeldung.com/sql-injection

방어하는 방법

  1. Parameter Binding (preparestatement 사용)
    • 순수한 Query String 을 사용하는 것이 아니라 Parameter Bindling 을 사용하면 피할 수 있다.
  2. mybatis에서 $를 사용하지 않고 #을 사용
  3. SQL 관련 에러가 발생했으르 때 해당 에러 메시지를 표시하지 않도록 한다.

Spring JPA 에서의 SQL injection 공격

Request Parameter 에 ' or 1'='1 을 덧 붙여 보내게 되면 서버에 저장된 모든 회원 정보를 가져올 수 있다.

http://localhost:8080/accounts?customerId=abc%27%20or%20%271%27=%271

저장된 회원 정보

@PostConstruct
public void init(){
Account account1 = Account.builder()
.customerId("account1")
.accNumber("010-1111-1111")
.build();

Account account2 = Account.builder()
.customerId("account2")
.accNumber("010-2222-2222")
.build();

Account account3 = Account.builder()
.customerId("account3")
.accNumber("010-3333-3333")
.build();

accountRepository.saveAccount(account1);
accountRepository.saveAccount(account2);
accountRepository.saveAccount(account3);
}

Controller

customerId 파라미터로 넘어온 데이터를 이용해 데이터 베이스에 저장된 회원 정보를 불러 오도록 한다.

@GetMapping("/accounts")
public List<AccountDTO> retrieveAccount(@RequestParam("customerId") String param){
log.info("Param = {}", param);
List<AccountDTO> accountDTOS = accountRepository.unsafeJpaFindAccountsByCustomerId(param);
return accountDTOS;
}

Repository

  • Controller 에서 넘겨준 customerId 를 이용해 SQL 을 작성하고 작성된 SQL 을 이용해 데이터베이스에 저장된 회원 정보를 가져온다.
  • Parameter 를 바로 받아 SQL 을 생성하는 이 부분에서 SQL Injection 문제가 발생하게 된다.
public List<AccountDTO> unsafeJpaFindAccountsByCustomerId(String customerId) {
// 직접 전달된 Parameter 를 이용해 생성하는 로직이 SQL Injection 의 원인이된다.
String jql = "select a from Account a where a.customerId = '" + customerId + "'";
log.info("Query = {}", jql);

TypedQuery<Account> q = em.createQuery(jql, Account.class);
return q.getResultList()
.stream()
.map(a -> AccountDTO.builder()
.accNumber(a.getAccNumber())
.customerId(a.getCustomerId())
.build())
.collect(Collectors.toList());
}

로그

로그를 확인해 보면 Parameter 로 같이 넘어온 ' or 1'='1 이 붙어서 쿼리문이 생성된 것을 확인할 수 있다.
생성된 쿼리가 수행될 경우 데이터 베이스에 저장된 모든 회원 정보를 가져오게 된다.

SQL Injection Log

결과적으로 모든 회원 정보가 반환되는 것을 확인할 수 있다.

모든 회원 정보

Share