여기 어때 해킹 사건을 파헤치다.

IT 이슈를 파헤치다|2017. 12. 29. 20:00

사건의 전말

여기어때는 200만 사용자가 사용하는 숙박업 서비스입니다.

지난 3월 16 ~ 17일 중국인 해커가 의뢰를 받고 청부해킹을 실행했는데요.

많은 사람들이 사용하는 서비스가 해킹된만큼 파장도 컸습니다.

개인정보 341만건이 유출되었고 유출된 정보를 통해 사람들에게 수치심을 일으키는 문자가 갔다고 합니다.

여기어때 측에 해커일당이 6억원 상당의 비트코인을 요구했지만 끝까지 대응하지는 않았다고 하네요.

이렇게 한번 신뢰를 잃으면 서비스 운영에 큰 타격이 있을 수 밖에 없겠죠?

그만큼 서비스 운영에서 보안은 중요합니다.

그럼 여기어때 해킹 사건에 어떤 해킹 수법이 쓰였는지 알아볼가요?

SQL Injection

SQL 인젝션은 코드 인젝션의 한 기법으로 분류됩니다.

인젝션은 “주입하다”라는 뜻을 가지고 있죠?

말 그대로 클라이언트의 입력값을 조작하여 서버의 데이터베이스를 공격하는 방식을 의미합니다.

서버는 사용자가 보낸 값을 받아 처리해서 요청에 대한 결과를 보내주게 되는데요.

이 요청에 '데이터베이스의 모든 값을 알려달라'라던가 '내가 사용할 수 있는 계정을 만들어라'같은 악의적인 명령문을 섞어 넣는 것이죠.

이 공격법은 단순히 클라이언트의 요청을 조작하기만 하면 되기 때문에 공격이 쉽습니다.

하지만 그 파괴력은 어마어마하죠.

시큐어 코딩(보안을 고려하면서 코드를 짜는 것을 의미합니다.)을 하는 개발자라면 가장 먼저 배우는 부분이기도 합니다.

어쩌면 기본적으로 방어해야 할 공격 방법이라고 볼 수 있는데요.

그럼 예를 보면서 SQL 인젝션 공격이 어떻게 이루어지는지 보도록 하겠습니다.

SELECT user FROM user_table WHERE id='무냐' AND password='나무';
SELECT user FROM user_table WHERE id='세피로트' AND password=' ' OR '1' = '1';  //무조건 TRUE가 됨 

거의 모든 웹 어플리케이션에 있는 부분이 바로 로그인 인증 부분입니다.

사용자는 아이디와 비밀번호를 입력하고 서버에서는 그 값을 받아 처리합니다.

사용자가 회원이면 다음 페이지로 넘어가고 아니면 일림창을 띄워주겠죠.

자, 이 과정을 좀 더 자세히 파헤쳐볼까요?

  • 사용자가 아이디와 로그인을 폼(양식)에 입력하고 전송버튼을 누른다.
  • 서버로 사용자가 보낸 아이디와 로그인 값이 도착한다.
  • 서버가 이 값과 데이터베이스에 저장된 값을 비교한다.
  • 일치하는 회원이 있으면 성공, 아니면 실패했다는 사실을 사용자에게 알려준다.

첫번째 쿼리문이 바로 사용자가 보낸 아이디와 비밀번호를 가지고 데이터베이스에서 찾는 내용입니다.

물론, 위의 쿼리문은 예시입니다.

실제 서비스에서는 절대 저런식으로 되어 있지 않죠.

개인정보보호법 29조를 위반하게 되거든요.

반드시 암호화해서 사용해야 합니다.

첫번째 쿼리문을 실행하면 아이디와 비밀번호에 해당하는 결과가 나오겠죠?


이제 두번째 쿼리를 봅시다.

이 쿼리가 해커에 의해 조작된 데이터가 온 경우입니다.

아이디가 '세피로트' 비밀번호가 ' OR '1' = '1'이라고 왔습니다.

위의 비밀번호가 의미하는 것은 다음과 같습니다.

따옴표를 닫아버렸기 때문에 비밀번호에 데이터가 들어가지 않고 그냥 끝나버립니다.

그 뒤에 OR '1' = '1'이라는 문구가 오는데 이것은 참을 의미합니다.

참이라는 의미는 무조건 올바른 값으로 판단하고 실행해라라는 의미와 같습니다.

즉, 서비스에 등록된 아이디와 비밀번호가 없어도 로그인이 완성되는 것이죠.

이것을 더욱 악용하면 전체 사용자의 데이터를 뽑아온다거나, 데이터베이스를 삭제해버리는 명령도 실행할 수 있습니다.

SQL Injection 대안


그럼 이 무선운 SQL 인젝션 공격을 어떻게 막을 수 있을까요?

  1. Database 접근 계정을 관리자 권한으로 사용하지 말 것.

    제한된 기능만 수행하는 계정을 만들고 이 사용자를 활용하여 쿼리를 수행할 것.

  2. 데이터베이스에서 수행하는 정보를 절대 출력하지 말 것. 오류를 다루는 함수를 사용하여 내부적으로 확인할 것.

  3. 사용자 입력값을 검증할 것.
  4. Prepared Statement를 사용할 것.


4번째의 Prepared Statement에 대해서 부가 설명을 드리겠습니다.

Prepared Statement

SQL을 데이터베이스가 이해하기 쉬운 형태로 만들어 놓은 것이라고 말씀 드릴 수 있겠네요.

다량의 쿼리를 수행하는 중대형 시스템에서 꼭 필요한 기능입니다.

Prepared Statement는 말 그대로 미리 준비된 선언문이라는 뜻입니다.

데이터베이스에서 Prepared Statement를 사용하기 위해서 내부적으로 해석하는 작업, 사용할 변수를 할당하는 작업을 처리하기 때문에 소형 시스템에서는 오히려 자원 낭비를 불러올 수 있습니다.

하지만 대형 시스템이 아니라도 Prepared Statement를 사용할 이유는 충분히 있습니다.

바로 SQL 인젝션을 막는 한가지 방법이 될 수 있기 때문이죠.

대부분의 SQL Injection이 발생하는 이유는 사용자가 보내는 변수가 검증이 되지 않았기 때문입니다.

원래라면 user_id와 password라는 변수에 자신의 아이디와 비밀번호를 입력하는게 정상적입니다.

하지만 악의를 가진 사용자는 여기에 쿼리문을 섞어서 보낼 수 있다는 것이죠.

Prepared Statement를 사용한다면 사용자가 보낸 악성 쿼리가 실행되는 것을 막을 수 있습니다.

먼저 SQL문이 데이터베이스 관리 시스템에서 수행되는 과정을 살펴보도록 하겠습니다.



  1. parse → 문법검사.
  2. bind → 값을 입력받아 변수 선언.
  3. execute → 디스크에 저장되어 있는 데이터 블록을 찾아서 메모리 버퍼에 복사.
  4. fetch → 메모리 버퍼의 데이터 블록에서 원하는 데이터를 추출하는 과정.

Statement와 Prepared Statement의 차이는 parse를 매번 하느냐 마느냐에 있습니다.

Statement는 위의 전과정을 매번 거칩니다.

하지만 Prepared Statement는 미리 parse과정을 거치게 됩니다.

이어서 자주 바뀌는 값을 변수로 선언해 두고 매번 다른 값을 대입(바인딩)합니다.

이 경우 Statement와는 다르게 사용자가 입력한 값이 문법적 의미를 가질 수가 없죠.

이미 문법검사는 parse과정에서 끝내놓고 완료된 결과값만 메모리에서 가져와 사용하기 때문입니다.

그렇기 때문에 사용자가 악의적인 쿼리문을 입력하더라도 그대로 실행되지 않는 것입니다.

이미 문법 검사는 끝이났고 사용자가 보낸 변수는 더 이상 쿼리문으로 해석되지 않기 떄문입니다.

그냥 에러가 나고 사용자에게 실패했다고 알려줄 뿐이죠.


이렇게 여기 어때 사건을 분석하고 공격 기법인 SQL 인젝션에 대해서 알아보았습니다.

빠른 개발도 좋지만 보안도 신경쓰지 않으면 안되겠죠? 

댓글()