
안녕하세요! 이번에는 SQL Injection에 대해 알아볼 것입니다. 무려 최근에도 자주 일어나는 SQL Injection에 대해서 이 공격이 어떤 공격인지에 대해 알아보도록 합시다.
SQL Injection을 알아보기 이전에, SQL 이라는 것이 무엇인지 부터 알아 봐야합니다.
SQL(Structured Query Language)란
관계형 데이터베이스에서 데이터를 관리하고 조작하기 위해 사용되는 표준 프로그래밍 언어
간단하게 예시를 들면 액셀에서 우리가 값을 넣고 이용할 수 있듯이 데이터베이스라는 저장소 내에 데이터라는 값을 넣거나 수정 삭제 등 사용자가 조작 할 수 있게 끔 할 수 있는 언어라고 할 수 있습니다.
SQL문은 명령에 따라 세가지로 분류 할 수 있습니다.
DB 구조 정의에 사용하는 언어. 테이블 뷰 및 인덱스 등의 DB 객체 생성 및 수정
DB의 기존 레코드를 수정
다른 사용자의 DB 권한을 관리 및 부여
이렇게 간단하게 SQL이란 어떤 것이고 SQL문의 종류에 대해 알아보았습니다. 사용법과 관련해서는 SQL에 관해 자세히 정리 되어있는 글을 참고하시기 바랍니다.
위에서 SQL에 대해서 간단하게나마 알아보았습니다. 그렇다면 SQL Injection이라는 공격방법에 대해 알아보겠습니다. SQL Injection은 공격자가 원하는 SQL문을 전달해주는 공격입니다.
이렇게만 얘기하면 이해하기 어려울 수 있으니 예시를 들어 봅시다.
호텔 방이 있습니다. 여러분들은 호텔 방에 방키를 들고 가서 문을 열 수 있습니다. 오직 각 방에 사용 할 수 있는 키로만 방문을 열 수 있습니다. 그러나 다른 사람이 갑자기 와서 방키도 없는데 자신이 가지고 있는 다른 무언가로 키를 열 수 있다면 어떻게 될까요? 이렇게 할 수 있는 것이 SQL Injection 인 것입니다.
SQL Injection에는 방법에 따라 네가지로 분류를 할 수 있습니다.
이렇게 있습니다.
먼저 일반적인 SQL Injection에 대해 알아봅시다.
다음 공격법은 로그인을 할 때라고 가정을 해보도록 하죠.

위의 사진에는 id와 password로 로그인을 할 수 있는 사이트입니다. 위의 사이트에서 로그인 버튼을 누르면 어떤 SQL 문이 동작할까요? 아마 밑의 SQL 문이 동작 할 것입니다.
1. SELECT ID FROM account WHERE ID='ID' AND PASS='PASS';
2. SELECT ID FROM MEMBER WHERE ID='ID';
#이후 패스워드 확인 절차
1번의 경우 아이디를 받아서 아이디에 맞는 비밀번호를 같이 동시에 확인을 하는 식별과 인증이 동시에 되는 방법입니다.
2번의 경우 아이디를 확인 한 이후(식별) 비밀번호를 따로 가져와서 확인을 하는 방법(인증)으로 식별과 인증이 분리된 방법입니다.
만약 아이디를 작성하는 칸에 이런 글자를 넣으면 어떻게 될까요?
test'#
아이디칸에 test#이라는 글자를 넣어보았습니다. 원래라면 일반적으로 아이디는 영어와 숫자로 이루어져 있을텐데 갑자기 #이라는게 등장했죠?
그렇다면 저 값을 SQL문에 한번 대입해봅시다.
2. SELECT ID FROM MEMBER WHERE ID='test'#' AND PASS='PASS';
다음과 같이 SQL문에 그대로 글자가 들어가게 된다면 #의 값이 뒤의 값을 주석처리 해버리기 때문에 문제가 생깁니다. 우리는 비밀번호에 정확한 값을 넣지 않았지만 로그인이 될 수 있다는 점.
이번에는 또 다른 방법으로 다음과 같은 글자를 비밀번호 칸에 넣으면 어떻게 될까요?
아무 비밀번호' OR '1'='1
이 값도 SQL문에 대입해봅시다.
SELECT ID FROM MEMBER WHERE ID='test' AND PASS='PASS' OR '1'='1';
위의 쿼리문을 한번 보면 AND와 OR이 같이 있습니다. 우선순위를 보면 AND가 OR보다 위에 있기 때문에 처음에 ID='test'라는 것, PASS='PASS'둘다 참이어야 참이기에 둘중 하나라도 틀리면 거짓이지만 이 값이 거짓이라도 뒤의 OR ‘1’=’1’ 이라는 조건 때문에 항상 참이 될 수 있습니다. 그래서 결론적으로 로그인에 성공하게 되는 것이죠.
이것이 바로 SQL Injection입니다.
다음은 Union SQL Injection입니다. 앞에 붙은 Union 이란 SQL에서 쿼리문의 결과를 한개로 합쳐주는 연산자입니다. 이 Union 연산자를 이용해서 우리가 원하는 데이터를 추출 할 수 있도록 해주는 기법입니다.
먼저 다음과 같은 게시판 창이 있습니다.

여기 검색어를 검색하면 제목에 맞게끔 검색이 됩니다. 그렇다면 검색을 했을 때 실행되는 SQL문은 어떻게 작성이 되어있을까요?
SELECT * FROM board WHERE 제목 LIKE '%검색어%'
이런 식으로 제목에 검색어가 포함되어있는 값이 밑에 출력이 되는 것 같습니다. 그렇다면 만약 검색어에 다음과 같은 글자를 넣는다면 어떻게 될까요?
%'#
이게 뭐지 싶겠지만 다음과 같은 텍스트를 SQL문에 함께 넣어봅시다.
SELECT * FROM board WHERE 제목 LIKE '%%'#%'
이렇게 되면 무엇이 검색이 될까요? SQL문에 따르면 board에 있는 모든 값들이 다 결과로 나올 것입니다. 참 신기하죠? 우리는 제대로된 검색어를 넣지도 않았고 단순히 특수문자 몇개만 넣었지만 똑같은 결과를 표출 한다는 사실이.
그렇다면 우리는 Union SQL Injection으로 무엇을 할 수 있을까요? 게시판. 결과값 도출. SQL. DB.
DB에 있는 값을 우리가 원하는대로 가져오게끔 할 수 있습니다. 차근차근 시작해보도록 합시다.
' order by 숫자#
처음으로 우리는 위의 order by문을 이용해 DB 내에 컬럼이 몇개가 있는지 확인 할 수 있습니다.
.' UNION SELECT 1,2,3,4,5#
이렇게 되면 각 컬럼에 맞게 어느 컬럼에 어떤 값이 학인이 가능한지 볼 수 있습니다.

필자의 경우 1,2,3,5가 각 값에 따라 표시되는 것을 확인
.' UNION SELECT database(),2,3,4,5#
다음과 같이 각 DB의 이름을 확인 할 수 있는 SQL문을 이용해서 DB명을 파악 할 수 있습니다. 필자는 WAMP를 사용하고 있으므로 Mysql에 사용되는 구문을 작성합니다.

다음과 같이 DB명이 tables라는 것을 확인 했습니다.
DB명을 확인 했으니 TABLE명을 확인 해봐야겠죠? 이 때 Mysql에서 TABLE명을 확인 할 수 있는
select table_name from information_schema.tables where table_schema='DB명'#
.' UNION SELECT table_name,2,3,4,5 from information_schema.tables where table_schema='tables'#
를 이용해서 알아보도록 합시다.

이렇게 DB안에 account와 board라는 두개의 테이블이 존재한다는 것도 확인 할 수 있습니다.
TABLE명을 확인 했으니 각 테이블에 어떤 컬럼들이 있는지 확인 해볼까요? 이 것 또한 Mysql에서 사용되는 방식을 사용합니다.
select column_name from information_schema.columns where table_name=’테이블명’#
.' UNION SELECT column_name,2,3,4,5 from information_schema.columns where table_name='account'#board도

다음과 같이 각 테이블의 컬럼들에 대해 확인 해 보았습니다.
이제 데이터들을 추출하기 위한 모든 값들을 알게 되었습니다. 이 값들을 가지고 저희는 보여지게끔 SQL 쿼리를 작성만 하기만 하면 되는 것입니다.
.' UNION SELECT aid,id,password,4,5 from account #
.' UNION SELECT title,writer,content,4,createdTime from board #


이렇게 우리가 원하는 데이터를 DB에서 가져올 수 있도록 하는 것이 UNION SQL Injection입니다.
다음은 Error Based SQL Injection입니다. 이름 그대로 SQL에서의 에러를 이용한 공격방법입니다. 그러나 여기 SQL에서는 두가지의 에러가 나타납니다. 첫 번째는 Syntax에러, 즉 구문(문법)오류와 두 번째는 로직(논리) 오류가 있습니다.
여기 에를 보면 두가지의 에러가 나와있습니다.


먼저 에러를 알기전에 에러가 어떻게 언제 발생하는지에 대해서 알아보도록 합시다.
첫 번째 에러는 컴파일 타임에 구문이 맞는지에 대해 확인하는 도중 에러가 발생한 예시입니다. SQL에서 컴파일 타임이란 코드가 실행되기 이전에 이 구문이 문법이 정확한가 에 대해 확인하는 것입니다. 이 문법이 실행 가능 하다면 다음으로 넘어갑니다.
이후 두 번째 에러로 런타임에서 일어나는 에러가 있습니다. 이 에러는 DB에서 작업중에 에러가 난 것 입니다. 그러하여 위의 예시와 같이 에러를 낼 수 있는 것입니다. 위의 예시는 강제적으로 에러를 일으켜서 select database()의 결과 값을 보여주게 하는 것입니다.
이렇게 런타임에서 일어나는 에러를 가지고 다음과 같이 우리가 원하는 SQL문을 전달해줄 수 있는 것입니다. 이 방법을 이용해서 이전에 이용했던 Union SQL Injection과 같은 구문들을 가지고 데이터를 추출 해낼 수 있습니다.
다음은 Blind SQL Injection입니다. Blind SQL Injection은 우리가 이전에 배웠던 Union, Error Based SQL Injection과는 다르게 결과값을 데이터의 온전한 값으로 볼 수 없을 때 사용할 수 있는 방법입니다. 이 때는 어떤 방법으로 데이터를 추출 할 수 있을까요??
어떤 곳에 SQL문에 따라서 참 일 때와 거짓 일 때 값이 다르게 출력될 때 Blind SQL Injection을 사용 할 수 있습니다. 예를 들어 원래는 아무 값이 나오지 않다가 참일 경우 유저의 아이디가 나온다라던가 또 다른 예시로는 항상 같은 값만 출력 하던 값이 거짓인 경우 값이 아예 나오지 않는 경우 일 수 있습니다.
이와 같은 경우 지금까지 우리는 order by 트릭과 select database() 와 같이 SQL문을 이용하여 값이 바로 나오게 사용을 하였지만 이제 우리는 다음과 같이 값을 추출 할 것입니다.

위의 그림과 같이 우리는 글자 하나하나가 참인지 거짓인지에 대해 판별 할 것입니다. 첫 번째 글자가 a인지 SQL문을 작성하고 맞다면 오른쪽 네모박스 내에 true였을 때의 값이 뜰 것이고 아니면 false였을 때의 값이 나올 것입니다. 예시 코드로 한번 확인 해보도록 합시다
ascii(substr((SQL),1,1))>0~127
여기서 먼저 알아야 할 것이 substr함수와 ascii함수가 어떤 역할인지에 대해 알아봐야합니다.
먼저 substr함수는 문자열을 자르는 함수입니다. substr((문자열),시작위치,갯수) 문자열을 시작위치로부터 갯수만큼 잘라서 반환을 해주는 것이죠. 예를 들어 문자가 “Hello” 시작위치가 1, 갯수가 1 이라면 H라는 문자가 반환이 됩니다. 갯수가 3이면 Hel이 반환되겠죠.
다음으로 ascii함수는 입력값을 ascii코드 값으로 출력해주는 함수입니다. 아스키코드표를 참고하여 A=65, a=97 이렇게 숫자로 반환이 되는 것 입니다.
결론적으로 이 함수를 어떻게 사용을 하냐면 아래의 코드와 같이 사용을 하게 됩니다.
1' and (ascii(substr((select database()),1,1))>0) and '1'='1
위의 코드와 같이 우리가 알아보았던 함수 앞과 뒤에 and로 참의 값을 작성하고(거짓일 때의 판별을 위해) 함수안에 우리가 알고 싶은 값의 SQL 쿼리문을 작성하고 글자 한개 한개를 추출하는 SQL Injection 공격입니다.
위의 방법을 사용했을 때 직접 해야할 일들이 굉장히 많아지기 때문에 자동화를 시켜서 값을 추출 하는 방법도 있습니다.
지금까지 SQL Injection의 3가지 방법에 대해 알아보았는데요.
이번에는 SQL Injection의 대응방법에 대해 알아보겠습니다.
Preparedstatement
Preparedstatement는 간단하게 SQL문에 글자를 그대로 넣을지 아니면 포장지로 감싸서 넣는 차이라고 생각하면 될 것 같습니다. 우리가 사용하는 SQL문을 한번 확인해 봅시다.
SELECT * FROM member WHERE ID=" ";
Preparedstatement를 사용하지 않았을 경우 우리가 작성한 SQL의 “ “ 안에 글자가 그대로 들어가기 때문에 이전에 배웠었던 SQL Injection이 가능했었습니다. 원하는 글자를 다 넣을 수 있기에 공격이 가능했던 것이죠.
그러나 Preparedstatement를 사용하게 된다면 우리가 넣을 값을 변수로 감싸고 후처리 후 넣어주기 때문에 SQL Injection이 일어나지 않습니다. 자세한 내용은 밑의 링크에서 자세히 알려주니 확인 해보시기 바랍니다.
https://tech.kakaopay.com/post/how-preparedstatement-works-in-our-apps/
화이트리스트 기반 필터링
만약 Preparedstatement를 사용 할 수 없는 곳이라면 우리는 화이트리스트 기반 필터링을 사용해야합니다.
블랙리스트
화이트리스트
블랙리스트 기반과 화이트리스트 기반 필터링이 있지만 공격을 방어하는 입장에서 금지 항목을 정하는 것보다 허용할 수 있는 것만 정하는 것이 위험도가 낮기 때문입니다. 하나하나씩 다 금지를 해도 공격자는 또 다른 새로운 공격방법을 찾을 것이고 우리는 하나하나를 다 막을 수 있긴 하겠지만 그것 보다는 처음부터 가능한 것만 정한다면 훨씬 더 간편하게 방어 할 수 있을 것입니다.