SQL Injection
SQL injection이란 Web hacking 기법 중 하나이다. Database에 질의하는 과정에 일반적인 값외에 악의적인 의도를 갖는 구문을 삽입하여 공격자가 원하는 SQL쿼리문을 실행하는 기법이다. 악의적인 사용자가 보안상의 취약점을 이용하여, 임의의 SQL 문을 주입하고 실행되게 하여 데이터베이스가 비정상적인 동작을 하도록 조작하는 행위 이며, 주로 사용자가 입력한 데이터를 제대로 필터링, 이스케이핑하지 못했을 경우에 발생한다.
2017년 3월에 일어난 "여기어때"의 대규모 개인정보 유출 사건도 SQL injection 으로 인해 피해가 발생했다. 해당 사건으로 예약, 제휴점, 회원 등 342만건(중복 제외시 99만건)에 달하는 가입자 정보 유출이라는 피해가 발생했다.
- Injection은 무언가를 주입/주사 할때 사용하는 단어이다. 따라서 SQL Injection은 SQL문을 이용해 웹 서버에 공격문을 주입하는 기법이라고 말할 수 있다.
- OWASP Top10에 항상 위치해 있을 만큼 빈번하게 일어나는 공격이며 그만큼 효과적이고 성공할 경우 큰 피해를 입을 가능성이 크다.
- 일반적으로 로그인 창, 검색창 등 입력이 가능한 곳에 SQL문을 넣어 입력값을 조작하는 공격으로 이루어진다.
- 문법이나 특징에 따라 여러 종류의 공격 기법이 존재한다.
SQL Injection 공격
SQL Injection 공격은 앞서 말한 바와 같이 문법이나 특징에 따라 여러 종류의 공격 기법이 존재한다. 그 중에서 가장 많이 사용되고 이해가 쉽고 간단한 Error Based SQL Injection으로 개념을 접해 보려고 한다.
SELECT * FROM Users WHERE id = 'INPUT1' AND password = 'INPUT2';
위의 쿼리문은 일반적으로 로그인 시 가장 흔하게 사용되는 SQL 구문이다. 해당 SQL 구문에 악의적인 사용자가 INPUT1에 해당하는 부분에 ' OR 1 = 1 --라는 임의의 SQL 구문을 주입했을 경우 DataBase에서는 아래와 같은 쿼리문이 실행될 것이다.
SELECT * FROM Users WHERE id = '' OR 1 = 1 --' AND password = 'INPUT2';
주입된 임의의 SQL 구문의 싱글쿼터로 WHERE절에 있는 id에 대한 부분의 싱글쿼터를 닫아 주고 그 이후의 password에 대한 부분은 --을 사용하여 주석처리를 하였다. 이로 인하여 1 = 1만 작동하게 되어 WHERE절이 항상 참으로 되는 결과를 얻을 수 있다.
매우 간단한 구문이지만, 결론적으로 Users 테이블에 있는 모든 정보를 조회하게 됨으로써 가장 먼저 만들어진 계정으로 로그인에 성공하게 된다. 보통은 관리자 계정을 맨 처음 만들기 때문에 관리자 계정에 로그인 할 수 있게될 가능성이 높다. 또한 관리자 계정을 탈취한 악의적인 사용자는 관리자의 권한을 이용해 또 다른 2차피해를 발생 시킬 수 있게 된다.
SQL Injection 공격의 종류
○Error based SQL Injection :
앞서 SQL Injection의 개념을 잡기 위해 언급한 예시가 Error based SQL Injection에 해당한다. 논리적 에러를 이용한 Injection방법이며, 가장 대중적이고 많이 쓰이는 SQL Injection이다.
○Blind based SQL Injection :
현재 많은 애플리케이션은 보안 목적으로 DataBase에서 출력하는 오류 메시지를 보이지 않게 하고 있다. 이렇게 DataBase에서 어떠한 메시지도 전달 받지 못하고 결과를 보고 참과 거짓만 판단할 수 있을 시 Blind based SQL Injection을 주로 사용한다.
테이블 명을 알고 싶을때 input창에 ' and 1=1 --과 'and 1=2 --의 SQL구문을 넣어보고 참일 때와 거짓일 때의 출력값을 알아낸다. 그리고 'and ASCII(SUBSTR(SELECT name FROM Table)name from Table limit 0,1),1,1)) <67과 같은 방식으로 참이 될 때까지 숫자를 변경해가며 테이블 명을 한 글자씩 알아내 간다. 이와 같은 방식으로 컬럼 수,테이블 이름 등을 알아 가는 방식을 Blind based SQL Injection이라고 한다. SLEEP, BENCHMARK등을 이용해 참과 거짓을 알아내는 방법도 있다.
○Union based SQL Injection :
해당 SQL Injection은 Union쿼리를 사용한 공격기법이다. 공격하는 쿼리와 다른 쿼리를 결합해 요청하여 하나의 테이블로 결과를 얻어 정보를 알아내는 방식이다. 해당 공격을 성공시키기 위해서는 동일한 필드 개수와 데이터 타입을 가져야하므로 사전 공격을 통하여 미리 알아놔야 한다.
게시글을 입력할 때 SELECT * FROM Board WHERE title LIKE '%INPUT%' OR contents '%INPUT%'와 같은 쿼리문을 통하여 DataBase에 요청을 보낸다고 가정했을 때 input창에 'UNION SELECT null,id,password FROM Users-- 를 넣어주게 되면 (이때 Board테이블의 칼럼 갯수에 맞추어 null이라는 빈 컬럼을 추가한 것) Board 테이블의 id, password를 알아낼 수 있다.
SQL Injection의 대응방안
○입력값 유효성 검사 :
SQL Injection의 가장 기본적인 대응 방안으로 모든 외부 입력값을 신뢰하지 않으며 항상 유효성을 검사하는 방법이다. 유효성 검사에는 블랙 리스트 검사와 화이트 리스트 검사방식이 있다.
- 블랙 리스트 검사 :
SQL 쿼리의 구조를 변경시키는 문자나 키워드, 특수문자를 제한하는 방식이다. SQL의 예약어인 UNION, GROUP 등의 함수명, 세미콜론이나 주석등의 특수 문자들을 미리 블랙리스트로 정의하고 블랙리스트에 정의되어 있는 키워드가 외부 입력값에 존재했을 경우 공백 등으로 치환하여 방어를 한다. 하지만 정교하게 입력값을 검증하지 않으면 블랙 리스트 방식의 검증을 우회할 수 도 있어 주의가 필요하다. 예를 들어 외부 입력값에 SESELECTLECT라는 외부 입력값이 들어왔을 시 중간의 SELECT가 공백으로 치환이 되어 좌우에 있는 모든 문자들이 합쳐저 SELECT라는 키워드가 완성된다.
- 화이트 리스트 검사 :
허용된 문자를 제외하고는 모두 허용하지 않는 방식으로 금지된 문자 외에는 모두 허용하는 블랙 리스트 방식보다는 훨씬 강력한 대응 방법이다. 하지만 웹 애플리케이션의 기능에 따라 알맞게 화이트 리스트를 작성해야 하는 번거로움이 존재한다.
○Prepared Statement 사용 :
일반적으로 SQL 쿼리의 실행 과정 아래와 같은 단계로 구성되어 있다.
1.구문분석(parsing)
2. SQL최적화(optimization)
3. 실행 계획을 실행 가능한 코드로 포맷팅(Row Source Generation)
4. 실행(execute)
5. 인출(fatch)
매번 동적으로 쿼리를 생성하는 일반적인 Statement를 사용하게 되면 구문분석부터 인출까지의 모든 과정을 수행핸다.
Prepared Statement란 미리 쿼리에 대한 컴파일을 수행하고 입력값을 나중에 넣는 방식이다. 즉, 일반적인 Statement와는 달리 불필요하게 모든 과정을 수행하지 않고 실행 전까지 과정을 최초로 1번만 수행한 후 실행 전에 미리 컴파일되어 있는 Prepared Statement를 가져다 쓰는 것이다.
Prepared Statement는 자주 수행되는 복잡한 쿼리들의 성능을 개선하기 위해 많이들 사용하지만, SQL Injection에 대한 대응 방법으로서 역할을 하기도 한다. Prepared Statement를 활용하면 쿼리의 문법 처리 과정이 미리 컴파일이 되어 있기 때문에 외부 입력값으로 SQL 관련 구문이나 특수문자가 들어와도 그것은 SQL 문법으로서 역할을 할 수 없기 때문이다.
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM USER WHERE ID = ? AND Password = ?")
위 코드에서 실제 쿼리 변수가 들어갈 자리에는 ?로 써져 있어서 특정값은 지정하지 않은 채로 남겨지게 되는데 변수 stmt가 생성되는 순간 연결되어 있는 DBMS에서는 위 쿼리에 대해 컴파일을 수행해 놓는다. 컴파일된 결과는 템플릿 같이 사용되며 나중에 저 쿼리가 실제로 수행되어야 하는 순간 넘겨받은 입력값을 ?에 바인드시켜주고 쿼리가 실행된다.
○오류 메시지 출력 제한 :
말 그대로 오류 메시지를 출력하는 것에 제한을 두는 것이다. 구체적으로는 다음과 같은 방법이 있다.
- DB 관련 오류를 사용자에게 노출되지 않게 커스텀 오류 페이지를 만들어야 한다.
- 너무 자세한 안내 메시지를 지양한다. 가령 로그인 실패 시 "패스워드가 일치하지 않습니다"라는 메시지를 띄울 경우 악의적인 사용자는 ID는 실제로 존재한다는 것을 알 수 있다. 이러한 구체적인 안내 메시지보다는 "로그인에 실패하였습니다"와 같이 추상적인 메시지를 띄우는 것이 안전성 측면에서 더욱 효율적이다. 다만 추상적인 메시지는 사용자의 편의성 및 사용성을 떨어뜨릴 수 있다.
○ DB보안
- 관리자가 사용하는 DB계정과 웹 애플리케이션이 DB에 접근할 때 사용하는 계정을 분리시켜야 한다.
- 분리시킬 뿐만 아니라 분리된 웹 애플리케이션이 사용하는 DB계정에 최소 권한만을 부여해줘야 한다.
'보안' 카테고리의 다른 글
CSRF(Cross Site Request Forgery) (0) | 2022.02.14 |
---|