SQL

RDBMS(Relation DataBase Management System)의 데이터를 관리하기 위해 설계 된 특수 목적 프로그래밍 언어로써 쉽게 말해 데이터베이스를 핸들링 하기 위해 사용하는 언어입니다.


SQL Injection

응용 프로그램 보안 상의 허점을 의도적으로 이용해, 개발자가 생각지 못한 SQL문을 실행되게 함으로써 데이터베이스를 비정상적으로 조작하는 코드 인젝션 공격 방법입니다.

[출처] 위키백과(https://ko.wikipedia.org/wiki/SQL_%EC%82%BD%EC%9E%85)


Blind SQL Injection

SQL Injection의 결과를 이용하여 참, 거짓 값을 구별 할 수 있을 때 사용할 수 있는 방법으로 장님이 길을 지팡이로 짚으며 가는 듯한 과정이 있어 이를 Blind SQL Injection이라고 합니다.


1. 대상 확인

언제나 처럼 게시판을 이용하여 정리해보도록 하겠습니다.

먼저 게시판의 검색 부분을 살펴보겠습니다.

검색조건을 선택하고 검색 키워드를 입력한 후 검색 버튼을 누르면 해당하는 내용을 검색 할 수 있습니다.

검색 조건은 작성자 / 키워드 / 작성자 + 키워드이며 공란인 경우 아래와 같은 알럿메시지를 표출합니다.

검색에 이용되는 키워드는 쿼리 작성에 사용이 될텐데 검색 후 URL에 파라미터가 표시되지 않는 것으로 보아 POST방식으로 파라미터가 전달 되고 있는 것 같습니다.

검색 할 당시의 패킷을 Burp Suite로 확인 해보도록 하겠습니다.


위에서 추측한대로 검색에 관련된 파라미터들(검색 조건/검색 키워드)이 POST방식으로 전달되고 있는 것을 확인 할 수 있습니다. (searching_condition=title&searching_keyword=TE)


2. 우회 과정

이 키워드에 전달되는 파라미터에 쿼리를 인젝션하여 특정 게시물의 비밀번호를 알아내보겠습니다.

결론부터 이야기하자면 인젝션하여 서버에서 실행 될 쿼리의 예시는 아래와 같습니다.

SELECT letterNum, userID, letterTitle, secretYN
FROM board
WHERE userID LIKE '%USER%'

AND ((ascii(substr((SELECT letterPW from board where letterNum = 6), 1, 1))) = 48)#%'

현재 서버에서 실행되고 있는 쿼리는 

SELECT letterNum, userID, letterTitle, secretYN
FROM board
WHERE userID LIKE '%{INPUT}%'

의 형태이고, 이 INPUT안에 쿼리를 삽입하여 위처럼 공격 쿼리를 만들어 내야합니다.

(지금은 직접 개발한 게시판이기 때문에 DB정보와 쿼리문의 구조를 알고있지만,

실제로는 추측/유추 해야하는 과정이 필요합니다.)

따라서 INPUT에 삽입될 쿼리는 아래와 같습니다.

USER%'

AND ((ascii(substr((SELECT letterPW from board where letterNum = 6), 1, 1))) = 48)#%'

AND 뒷 부분이 핵심이므로 이 부분을 상세히 살펴보도록하겠습니다.

1) SELECT letterPW FROM board WHERE letterNum = 6

-> 이 예시에서는 게시글 번호 6번 글에 대한 패스워드를 유추하고 있습니다. 따라서 6번 게시물의 패스워드를 조회해 옵니다.


2) substr( 6번게시물 패스워드, 1, 1)

-> mysql에서 실행되는 함수로 문자열의 첫 번째 글자부터 한 글자를 조회해옵니다.

-> 결과적으로 6번 게시물 패스워드의 맨 첫 글자를 가져올 수 있습니다.


3) ascii(문자)

-> 해당 문자를 아스키 코드 값으로 변환해줍니다.


4) 결과

6번 게시물 패스워드의 첫 글자의 아스키코드가 아스키코드 값 48과 같은 지를 반환합니다.

위의 결과가 true인 경우, 원래의 쿼리와 결과가 종합되어 모든 게시물이 출력되고(true and true이므로),

false인 경우 게시물 조회 결과가 없음으로 표시됩니다.


패스워드를 한 글자씩 찾는 스크립트가 필요합니다. 

스크립트 풀 소스는 첨부 하고, 주요 부분만 확인하도록 하겠습니다.  : )

첨부 :  blinSqlInjection.py

실습환경이기때문에, 비밀번호는 숫자와 영대소문자로만 이루어져있다는 가정을 하였습니다.

스크립트는 파이썬으로 작성하였으며 스크립트 실행 전 설치해야할 파이썬 라이브러리로는

http리퀘스트를 위한 requests와 결과를 파싱하기 위한 Beautifulsoup가 있습니다. 


헤더 값은 Burp Suite에서 얻은 패킷의 값과 동일하게 맞추어주고, 파라미터를 변경합니다.

우리가 수정해서 삽입할 쿼리를 완성시킨 executeSql을 키워드 파리미터에 셋팅해줍니다.

requests를 이용하여 리퀘스트를 보내고 받은 결과를 BeautifulSoup를 사용하여 파싱합니다.

해당 게시판의 list.jsp 응답 내용 중 "총 게시물 : x개" 로 body에 응답이 오는데 이 부분을 파싱하여

0개인 경우에는 찾지 못한 것으로, 0개 이상인 경우에는 찾은 것으로 판별하였습니다.


아래는 파싱하여 게시물의 갯수를 int형으로 변환하여 리턴해주는 함수입니다. 

첫 째줄에서는 공백을 제거하였고, ":"로 스플릿한 뒷부분을 공백제거하여 이 후

첫 글자를 int형으로 변환한 후 리턴해줍니다.


비교할 검사 문자는 0~9, a ~ z, A ~ Z 입니다. 각 문자들이 아스키 코드표 상 연속되어 존재하지 않기 때문에

구간 구간 점프를 뛰어야하는 부분을 if문으로 걸러주었으며, 찾는 문자 구간에 문자가 없으면 패스워드의 끝으로 인식하고 있습니다. ( elif int(asciiNum) > 122 부분)

문자를 찾은 경우에는 targetPW라는 필드에 찾은 문자를 덧 붙혀주고, 검사 문자 순서를 다음으로 넘긴 후

비교 문자를 0('48')로 초기화 시켜주고, 쿼리문을 갱신합니다.


위 과정을 모두 마친 후 추출해낸 패스워드를 확인합니다.


스크립트 실행 결과


3. 조치 방법

1. jspBoardProject#2(XSS) 에서 조치했던 것 처럼 입력 값에 필터링을 수행하여 검사할 수 있습니다.

2. JAVA/JSP에서는 쿼리 실행을 Statement클래스를 사용하지 않고 PreparedStatement를 사용하는 방법이 있습니다.


필터링에 관해서는 앞서 다뤄본 방식과 별반 다르지 않기 때문에, PreparedStatement를 사용하는 방법으로 조치해보도록 하겠습니다.

먼저, 문제가 되는 부분의 소스코드를 살펴보면,

아래와 같이 변수가 쿼리에 직접적으로 연결되는 구조로 Injection하기 너무나 편리하게 되어있는 것을 확인 할 수 있습니다.



이 부분을 PreparedStatement 인터페이스를 사용하여 다음과 같이 조치 할 수 있습니다.

바뀐 점은 일반 Statement인터페이스에서는 쿼리와 Input value가 직접적으로 연결되어 실행이 되지만,

PreparedStatement에서는 쿼리문에서는 ? 로 인자 표시를 하고 후에 setString과 같은 메서드를 사용하여

변수를 추가 해주고있습니다.

코드 수정 후 스크립트 결과를 보면 아래와 같이 조치가 된 것을 확인 해볼 수 있습니다.



여담.

order by 뒷 부분 역시 PreparedStatement방식으로 사용 해보려고했으나, setString(4, sorting_cond); 와 같은 방식으로 추가했을 때 sorting_cond가 상수 값으로 인식되어 정상적으로 정렬이 되지 않았습니다.

외부에서 직접적으로 입력을 받는 부분이 아니기때문에 문제가 없을 것으로 생각되어 기존처럼 쿼리에 연결을 하였습니다.

'Study > WEB' 카테고리의 다른 글

jspBoardProject#5(File Download)  (0) 2017.02.08
jspBoardProject#3(파라미터 변조)  (0) 2017.02.06
jspBoardProject#2(XSS)  (0) 2017.02.06
jspBoardProject#1  (0) 2017.02.06

+ Recent posts