본문 바로가기
ETC/Security

[웹 보안] SQL Injection

by Gnaseel 2020. 2. 24.
728x90
반응형

웹 보안 카테고리 안에 포스팅을 했지만, sql injection은 데이터베이스와 연동되는 모델이라면 언제나 발생할 수 있는 문제이다.

 

간단하게 결론부터 말하자면 SQL injection은 DB에접근할 때 사용자가 입력하는 값이 쿼리문에 포함될 때 악의적으로 입력값을 설정해서 쿼리문을 조작해, 비정상적으로 DB를 동작시키는 해킹 방법이다.

 

여기서 우리는 SQL injection의 두가지 전제조건을 발견할 수 있다.

1. 데이터베이스가 존재하고, 모델과 연동되어야한다. ( 당연하다. 애초에 쿼리문을 조작하는 해킹방법인데 DB가 없다면 모순)
2. 사용자의 입력 값이 쿼리문에 포함되어야 한다. 

 

유감스럽게도, 대부분의 웹 어플리케이션은 위 두 조건을 만족한다. 로그인 기능 하나만으로도 위 두 조건은 충족시킬 수 있다.

회원으로 활동하려면 회원의 id와 pw를 저장할 DB가 있어야하고,

로그인 할 때 id와 pw가 일치하는지 확인하려면 사용자가 로그인 폼에 입력한 id와 pw 데이터를 받아와서 DB와 동일한지 검증하는 절차가 있어야 하기 때문이다.

 

SQL injection은 어설퍼보이지만 내부 DB의 정보를 탈취하거나, 허가되지 않은 권한을 얻을 수 있어서, 보안이 되어있지 않은 어플리케이션에 대해서는 매우 치명적이다.

 

OWASP에서는 3~4년을 주기로 웹 어플리케이션의 취약점 중 치명적인 결함을 줄 수 있거나 빈도가 빈번한 10개를 순서를 매겨 발표하는데, 2010년 이후로는 항상 SQL injection이 당당하게 1위를 차지하고있다.

(물론 그 전에도 항상 순위권 내 존재하던 취약점이었다.)

 

즉 SQL injection은 옛날에나 통하던, 이론적으로 배워야하는 개념이 아니라 최근에도 빈번하게 문제가 발생하고있는 이슈라는 뜻이다. 

실제로 국내로 예를 들자면, 뽐뿌나 여기어때 등 영세하다고 할 수 없는 기업의 DB마저도 SQL injection으로 돌파된 전례가 있다.

 

그렇다면  SQL injection이 실제로 어떻게 동작하는지 살펴보자.

 

로그인을 할 때 일반적으로 다음과 같은 쿼리가 사용된다.

query

Select * from USER Where ID='inputID' AND PW='inputPW'

입력한 id와 pw에 해당하는 인스턴스를 찾고, null이 아니면 로그인이 허용되는 방식이다.

 

 

1. 조건 무력화

 

  1-1. 쿼리문 주석화

 

이어지는 쿼리문을 주석화 시켜서 비정상적으로 동작하게 하는 방법이다.

예를들어 id가 Gnaseel인 계정을 해킹하고 싶다면, 

ID   : Gnaseel'--

PW : 임의

라고 입력하는 것이다. PW는 임의의 아무 데이터를 넣어도 문제없다.

 

위와 같이 입력 한다면 쿼리문은

Select * from USER Where ID='Gnaseel'--' AND PW='임의'

의 형태가 될 것이다.

 

--뒷부분은 주석처리가 되기 때문에 인식되지 않고, DB에게 주어진 명령은

특정 ID와 PW를 비교하는 작업에서 특정 ID가 존재하는지 판단하는 작업으로 바뀔 것이다.

물론 Gnaseel이라는 유저는 존재하기 때문에 인스턴스를 반환할 것이고,

인스턴스를 반환받은 모델은 해당 유저로 로그인 시킬 것이다.

뒷부분에 나올 대부분의 방법이 이 주석화를 기본으로 사용하게 된다.

 

  1-2. 논리 삽입

 

심지어 ID를 몰라도 로그인 한 것 처럼 로그인 폼을 통과시킬 수도 있다.

 

ID   : 임의' OR 1=1 --

PW : 임의

로 입력한다면 어떻게될까. 다들 알다시피 임의라는 id는 DB에 없다.

 

DB가 입력받은 명령이 어떻게 변했는지 살펴보자.

Select * from USER Where ID='임의' OR 1=1 --' AND PW='임의'

 

DB가 받은 명령은 어느새 '임의'인 ID가 존재하거나, 참인 경우 라는 명령이 되었다.

id가 어찌되었든 1=1의 조건이 충족되니 존재하는 모든 인스턴스를 반환할 것이다.

 

2. SQL 명령어 삽입

 

  2-1. UNION 삽입

 

가장 빈번하게 사용되는 삽입중 하나로, union 명령어를 쿼리에 삽입하는 것이다.

union 명령어는 두 개의 쿼리문의 결과를 하나로 통합해서 출력해주는 명령어인데, 이 명령어를 사용해서 DB내부의 데이터를 얻을 수 있다.

예를들어 제품의 코드와 이름이 담긴 테이블에서 제품 이름을 검색하는 쿼리문은

Select * from STUFF Where NAME='inputName'

의 형태이다.

 

Name : ' OR 1=1 Union select * From USER

여기서 입력값에 union 명령어를 사용한다면

 

DB가 받는 명령은

Select * from STUFF Where NAME='' OR 1=1 Union select * From USER

가 되고, 모든 제품의 코드와 이름이 사용자의 id, pw와 함께 노출될 것이다.

 

물론 컬럼의 개수나 데이터형에 따라 사용법이 나뉘겠지만 대강 이러한 방식으로 접근하는 것은 마찬가지다.

 

 

3. 에러 발생 유도

 

SQL injection을 에러가 나는 방법으로 사용하고, 유도된 에러의 메세지를 통해 DB의 구조를 알아내는 방법이다.

 

아까 사용했던 

Select * from USER Where ID='inputID' AND PW='inputPW'

만 하더라도, 

 

ID : 임의--

 

값을 넣어버린다면

 

DB는

Select * from USER Where ID='임의--' AND PW='inputPW'

로 인식할 것이고, 해커에게 친절하게 ID 컬럼에서 문자열이 닫히지 않았다는 에러 메세지를 보내 줄 것이다.

이렇게 컬럼 명이 유출된다.

 

이외에도 많은 방법들이 있다.

 

그렇다면 SQL injection을 어떻게 방어해야 할까.

우선 가장 손쉬운 방법은 사용자에게 입력받는 값을 DB에 전달하는 쿼리문으로 만들기 전에 문제가 될 만한 특수문자를 차단하는 것이다.

' - # * / 같이 쿼리문에 영향을 조금이라도 끼칠 수 있는 특수문자를 모델에서 판별하고, 거부하거나 필터링 한다면 SQL injectino에 대해 효과적으로 대비할 수 있다.

 

두 번째 방법은 에러 메세지를 노출하지 않는 것이다.

에러 메세지는 개발자가 에러의 이유를 쉽게 이해할 수 있도록 많은 정보를 제공해주기 때문에 외부인에게 탈취당한다면 큰 문제가 된다.

개발과 배포에 차이를 둬서 배포되는 어플리케이션에서는 정보를 알 수 없도록 커스텀된 에러메세지를 반환하게 해서 DB의 구조가 밝혀지는 것을 방어할 수 있다.

반응형