상세 컨텐츠

본문 제목

[보안 공부] 7주 차 - Error Based, Blind SQL Injection

컴퓨터/보안 수업(Segfault)

by 디멘터 2024. 6. 5. 16:34

본문

※ 주의 : 보안 관련 학습은 자유이지만, 학습 내용을 사용한 경우 법적 책임은 행위자에게 있습니다.
(Note: Security-related learning is allowed, but the individual is solely responsible for any legal consequences arising from the use of the acquired knowledge.)

※ 공부 기록용 포스트이며, 검수를 하고 올리지만 간혹 잘 못된 정보가 있을 수도 있습니다.

[보안 공부] 7주 차 - Error Based, Blind SQL Injection

제가 사용하는 DB는 MySQL이랑 MariaDB입니다. 다른 DB에서는 방식이 조금 다를 수 있습니다.

여기서는 개념 위주 설명이고, 자세한 공격기법은 CTF 문제에서 사용하겠습니다.


SQL injection (Error Based, Blind)

 

1.Error Based SQL Injection

▶Error Based SQL injection

→ 에러기반 SQL 인젝션의 핵심은 아래와 같다.

  • ①SQL결과를 나타내는 곳에 의도적으로 Error를 유도 한 뒤
  • ②Error 메시지 안에 서브쿼리를 삽입하여 유효한 SQL을 실행시키고,
  • ③Error를 표출하면서 실행된 서브쿼리로 데이터를 알아낸다.

→ 왜 이렇게 복잡하게 공격하느냐..?

지난 시간 무적의 LIKE와, UNION SELECT 구문을 이용하면 DB를 쉽게 털 수 있지 않은가?

대표적으로 SQL쿼리 결과를 직접적으로 보여주지 않고, 그런 값이 존재합니다 or 존재하지 않습니다 정도만 알려주는 경우, UNION을 써봤자 소용이 없다.

→ 아래 더 자세한 이유 참조.

 

  • 에러 메시지 제공: 일부 시스템에서는 SQL 쿼리의 결과가 직접적으로 사용자에게 노출되지 않는 경우가 있습니다. 이럴 때는 UNION SELECT 방법을 사용하더라도 결과를 확인하기 어렵습니다. 그러나 시스템이 에러 메시지를 반환한다면, Error Based SQL Injection을 통해 유용한 정보를 얻을 수 있습니다.
  • 필터링과 차단: 많은 웹 애플리케이션 방화벽(WAF)이나 입력 필터링 메커니즘이 UNION 키워드를 차단하거나 감지할 수 있습니다. 이런 경우, UNION SELECT 방식은 사용하기 어려워집니다. 반면에, Error Based SQL Injection은 다른 방식으로 쿼리를 구성하기 때문에 탐지를 피할 수 있습니다.
  • 결과 표현의 제한: UNION SELECT는 결과가 하나의 결과 집합으로 반환될 때 유용합니다. 그러나 때로는 결과를 특정 방식으로 표현하거나 필터링해야 할 필요가 있습니다. Error Based SQL Injection은 이런 제한을 극복할 수 있는 유연성을 제공합니다.
  • 애플리케이션 로직에 따른 접근성: 일부 애플리케이션은 UNION SELECT와 같은 명령을 통해 데이터에 접근하는 것을 제한할 수 있습니다. 예를 들어, 특정 사용자 입력 필드에서만 SQL Injection이 가능하고, 이 필드가 결과를 직접적으로 반환하지 않는 경우가 있을 수 있습니다. 이런 경우에 Error Based SQL Injection은 유용할 수 있습니다

 

 

▶Error Based SQL injection의 예시 1(GROUP BY사용)

SELECT * FROM users WHERE id = '1'

→ 이런 SQL이 있을 때, 사용자는 1 대신 원하는 값을 넣을 것이다.

실전에서는 따옴표와 주석을 적절히 활용하여 아래와 같은 구문을 만들면 된다.

SELECT * FROM users WHERE id = '1' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT database()), 0x3a, FLOOR(RAND()*2)) AS x FROM information_schema.tables GROUP BY x) AS y)

→ 이렇게 에러를 유도할 수 있다.

 

→ 여기서 에러를 유도하기 위해 사용된 구문은 GROUP BY이다.

에러가 발생하는 이유는 rand함수를 포함한 서브쿼리를 넣음으로, GROUP BY절이 예상치 못한 방식으로 중복데이터를 생성하기 때문이며 이로 인해 MySQL이 중복된 키를 처리하지 못해서 에러가 발생한다.

말이 좀 난해한데 쉽게 말해서 그룹으로 묶고 싶은데 못 묶고 에러가 발생한다라고 이해하면 좋을 것 같다.

이해하기 위해서 열심히 알아보고 여러 케이스로 SQL을 실행해봤는데 시원하게는 알아내지는 못했다. 중복처리에 에러가 있는 듯 하다.

 

→ DATABASE()는 데이터 베이스 이름을 반환하는 함수이다. 공격 시 데이터베이스 이름, 테이블 이름, 칼럼(속성) 이름이 필요하기 때문에 TOP인 데이터베이스 이름부터 차근차근 알아내는 것이다.

CONCAT은 단순히 문자열을 연결시켜 주는 함수이다. 여기서 인자값에 0x3a는 아스키코드로 콜론(:)이다. 

→ FLOOR() 함수는 주어진 숫자보다 작거나 같은 최대 정수를 반환하는 함수이다.

  • FLOOR(1.7)인 경우 1을 반환한다.
  • FLOOR(-2.4)인 경우 -3을 반환한다. 

→ RAND() 함수는 MySQL에서 난수를 생성하는 함수이다. 인자 값을 주지 않으면 0과 1 사이의 무작위 실수를 반환한다. 인자로 0을 주게 되면, 시드값 0을 이용하여 예측 가능한 난수를 생성한다. (해보니 시드가 같으면 같은 값이 나온다.)

내가 가진 테스트용 DB에서 테스트를 해본다.

 

SELECT * FROM test_table WHERE 학생이름 = '유라' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT database()), 0x3a, FLOOR(RAND()*2)) AS x FROM information_schema.tables GROUP BY x LIMIT 1) AS y);

 

→ 이 구문으로 SQL 인젝션 공격을 했더니 아래와 같이 GROUP BY 작동 때문에 에러를 출력한다. 랜덤 함수가 들어가 있어서 한번씩은 정상 실행되기도 한다.

에러 1, 2 케이스. 랜덤 함수 기반이라서 쿼리가 정상 실행되기도 한다.

 

→ DATABASE() 함수의 결과 값인 DB이름 'test'가 출력되었다.

→ 이 에러가 사용자단에 표시되면 Error Based SQL injection이 시작된다.

→ SELECT DATABASE() 대신 SQL 쿼리를 넣으면 결과 값들이 나오게 된다.

아래와 같이 학생이름 첫 행을 가져오라고 해보았다.

SELECT * FROM test_table WHERE 학생이름 = '유라' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT 학생이름 from test_table limit 0,1), 0x3a, FLOOR(RAND()*2)) AS x FROM information_schema.tables GROUP BY x LIMIT 1) AS y);

친절하게 에러 메시지에 있는 학생이름 첫 행을 알려주었다.

 

 

▶Error Based SQL injection의 예시 2 (EXTRACTVALUE 함수 사용)

 

→ EXTRACTVALUE 함수는 XML포맷에서 데이터 값을 가져오는 함수이다.

→ EXTRACTVALUE(xml_fragment, xpath_expression)

EXTRACTVALUE(XML데이터가 포함된 문자열, XML 데이터에서 추출하고자 하는 값을 지정하는 XPath표현식)

  • XML데이터 예시
    <root> <element attribute="value">Text</element> </root>
  • 예제 테이블 생성 및 데이터 삽입 예시
    CREATE TABLE xml_data ( id INT AUTO_INCREMENT PRIMARY KEY, data XML );
    INSERT INTO xml_data (data) VALUES ('Text');
  • XML 데이터 추출 예시
    SELECT EXTRACTVALUE(data, '/root/element') AS extracted_value FROM xml_data;
  • 에러 유도 및 데이터베이스 명 추출 예시(SQLi)
    SELECT EXTRACTVALUE(NULL, CONCAT('!', (SELECT database()), ']'));
    느낌표 같은 특수문자를 끼워 넣는다.

→ 아래 문구로 내 DB에 공격을 해본다.

SELECT * FROM `test_table` WHERE 학생이름='유라' and EXTRACTVALUE(NULL, CONCAT('!', (SELECT database()), ']'));

 

DB이름이 출력 되었다.

 

XPATH 문법 에러를 띄웠고, 이번에도 순순히 DB이름을 내주었다.

→ 이 에러메시지가 사용자에게 표시되면 공격을 당하는 것이다.

→ 마찬가지로 SELECT database() 대신 원하는 쿼리를 넣으면 된다.

 

★생각해 보기
→ 가만히 생각해 보니, 함수에서 경로를 표현하는 부분에서 SQL이 실행되었음을 볼 수 있다.
→ 그렇다면 왜? 경로를 문자열 정로도로 처리하지 않고 명령어처럼 실행, 해석하여 이런 문제를 발생시키는지 알아보았다.
→ 동적 XPath 표현식이 문제였다. EXTRACTVALUE와 UPDATEXML 함수는 동적 XPath 표현식을 처리할 때 SQL과 같은 명령을 포함할 수 있었다.
→ 그렇다면 동적 XPath 표현식을 사용하지 못하도록 방어하는 방법은 없을까 알아보았다.
→ 입력 검증(화이트리스트, 블랙리스트 등), 준비된 문장사용, XML 파서 사용 하는 방법들이 있었다.

 

 

▶Error Based SQL injection에 사용되는 여러 함수들 (현재는 있다는 것만 보고 넘어가자)

 

→ MySQL

  • RAND()와 GROUP BY 사용 : 예시 1에서 행했던 방법. 중복 키 오류 유도
  • CONCAT() : 문자열 연결을 통해서 DB 정보를 에러 메시지에 삽입
  • EXTRACTVALUE와 UPDATEXML
  • CONVERT(): 잘못된 변환으로 에러를 유도

→ Microsoft SQL Server

  • ERROR_MESSAGE(): 에러 메시지를 통해 데이터 추출
  • CAST(): 잘못된 타입 캐스팅으로 에러 유도.
  • CHAR(): 잘못된 문자 변환으로 에러 유도.

→ Oracle

  • TO_NUMBER(): 잘못된 형 변환으로 에러 유도.
  • UTL_INADDR.get_host_address(): 잘못된 호스트 주소로 에러 유도.
  • DBMS_OUTPUT.put_line(): DBMS 출력 함수로 에러 유도.

→ PostgreSQL

  • PG_SLEEP(): 쿼리 실행을 지연시켜 시간 기반 공격을 유도.
  • TO_NUMBER(): 잘못된 형 변환으로 에러 유도.
  • RAISE: 직접 에러 메시지를 발생.
  • substring(): 잘못된 위치나 길이로 에러 유도.

 

2.Blind SQL Injection

 

▶Blind SQL injection

→ 이름만 봐도 고생할 것 같은 느낌이다.

→ 순서를 봐서 알겠지만 Error based SQLi가 안되면 쓰는 방법이기도 하다.

→ 블라인드 SQL인젝션에는 크게 두 가지가 있다.

  • ①Boolean-based Blind SQL injection
  • ②Time-based Blind SQL injection

 

▶Boolean-based Blind SQL injection

→ 참 거짓을 활용하는 SQL인젝션이다. 개인적으로 CTF-Login Bypass 3 문제에서 처음으로 시도했던 방법과 비슷하다.

→ 조건을 걸어서 참 또는 거짓을 유도해 보자

 

공격 구문을 만들어 본다.

SELECT * FROM test_table WHERE 학생이름 = '유라' AND SUBSTRING((SELECT database()), 1, 1) = 'a'

 

→ AND절 이하에서 참(True)이 나오면 학생이름이 유라인 행을 반환할 것이고 거짓이면 반환하지 않을 것이다.

 

SUBSTRING() 함수 참고 (인덱스가 1부터 시작임에 유의)

  • SUBSTRING(string, start, length)
  • SELECT SUBSTRING('Hello World', 1, 5); 결과는 'Hello'
  • SELECT SUBSTRING('Hello World', 7); 결과는 'World'

→ 결국 해당 공격은 데이터베이스 이름의 첫 글자가 a이냐 아니냐를 묻고 있는 것이다.

→ SELECT database() 대신 원하는 공격 구문을 넣으면 된다.

 

→ ASCII() 함수는 아스키코드로 변환하는 함수이다.

  • ASCII('A') 결과는 65
  • ASCII('AB') 결과도 65가 나온다. (첫 번째 문자만 취급한다)
  • ASCII('a') 결과는 97

→ 아래는 아스키코드표이다. (128개)

ASCII 코드표

10진수 16진수 8진수 문자 설명
0 0x00 000 NUL Null
1 0x01 001 SOH Start of Header
2 0x02 002 STX Start of Text
3 0x03 003 ETX End of Text
4 0x04 004 EOT End of Transmission
5 0x05 005 ENQ Enquiry
6 0x06 006 ACK Acknowledge
7 0x07 007 BEL Bell
8 0x08 010 BS Backspace
9 0x09 011 TAB Horizontal Tab
10 0x0A 012 LF Line Feed
11 0x0B 013 VT Vertical Tab
12 0x0C 014 FF Form Feed
13 0x0D 015 CR Carriage Return
14 0x0E 016 SO Shift Out
15 0x0F 017 SI Shift In
16 0x10 020 DLE Data Link Escape
17 0x11 021 DC1 Device Control 1
18 0x12 022 DC2 Device Control 2
19 0x13 023 DC3 Device Control 3
20 0x14 024 DC4 Device Control 4
21 0x15 025 NAK Negative Acknowledge
22 0x16 026 SYN Synchronous Idle
23 0x17 027 ETB End of Transmit Block
24 0x18 030 CAN Cancel
25 0x19 031 EM End of Medium
26 0x1A 032 SUB Substitute
27 0x1B 033 ESC Escape
28 0x1C 034 FS File Separator
29 0x1D 035 GS Group Separator
30 0x1E 036 RS Record Separator
31 0x1F 037 US Unit Separator
32 0x20 040 Space Space
33 0x21 041 ! Exclamation mark
34 0x22 042 " Double quotes
35 0x23 043 # Hash
36 0x24 044 $ Dollar sign
37 0x25 045 % Percent sign
38 0x26 046 & Ampersand
39 0x27 047 ' Single quote
40 0x28 050 ( Left parenthesis
41 0x29 051 ) Right parenthesis
42 0x2A 052 * Asterisk
43 0x2B 053 + Plus
44 0x2C 054 , Comma
45 0x2D 055 - Hyphen
46 0x2E 056 . Period
47 0x2F 057 / Slash
48 0x30 060 0 Zero
49 0x31 061 1 One
50 0x32 062 2 Two
51 0x33 063 3 Three
52 0x34 064 4 Four
53 0x35 065 5 Five
54 0x36 066 6 Six
55 0x37 067 7 Seven
56 0x38 070 8 Eight
57 0x39 071 9 Nine
58 0x3A 072 : Colon
59 0x3B 073 ; Semicolon
60 0x3C 074 < Less-than
61 0x3D 075 = Equals
62 0x3E 076 > Greater-than
63 0x3F 077 ? Question mark
64 0x40 100 @ At symbol
65 0x41 101 A Uppercase A
66 0x42 102 B Uppercase B
67 0x43 103 C Uppercase C
68 0x44 104 D Uppercase D
69 0x45 105 E Uppercase E
70 0x46 106 F Uppercase F
71 0x47 107 G Uppercase G
72 0x48 110 H Uppercase H
73 0x49 111 I Uppercase I
74 0x4A 112 J Uppercase J
75 0x4B 113 K Uppercase K
76 0x4C 114 L Uppercase L
77 0x4D 115 M Uppercase M
78 0x4E 116 N Uppercase N
79 0x4F 117 O Uppercase O
80 0x50 120 P Uppercase P
81 0x51 121 Q Uppercase Q
82 0x52 122 R Uppercase R
83 0x53 123 S Uppercase S
84 0x54 124 T Uppercase T
85 0x55 125 U Uppercase U
86 0x56 126 V Uppercase V
87 0x57 127 W Uppercase W
88 0x58 130 X Uppercase X
89 0x59 131 Y Uppercase Y
90 0x5A 132 Z Uppercase Z
91 0x5B 133 [ Left bracket
92 0x5C 134 \ Backslash
93 0x5D 135 ] Right bracket
94 0x5E 136 ^ Caret
95 0x5F 137 _ Underscore
96 0x60 140 ` Backtick
97 0x61 141 a Lowercase a
98 0x62 142 b Lowercase b
99 0x63 143 c Lowercase c
100 0x64 144 d Lowercase d
101 0x65 145 e Lowercase e
102 0x66 146 f Lowercase f
103 0x67 147 g Lowercase g
104 0x68 150 h Lowercase h
105 0x69 151 i Lowercase i
106 0x6A 152 j Lowercase j
107 0x6B 153 k Lowercase k
108 0x6C 154 l Lowercase l
109 0x6D 155 m Lowercase m
110 0x6E 156 n Lowercase n
111 0x6F 157 o Lowercase o
112 0x70 160 p Lowercase p
113 0x71 161 q Lowercase q
114 0x72 162 r Lowercase r
115 0x73 163 s Lowercase s
116 0x74 164 t Lowercase t
117 0x75 165 u Lowercase u
118 0x76 166 v Lowercase v
119 0x77 167 w Lowercase w
120 0x78 170 x Lowercase x
121 0x79 171 y Lowercase y
122 0x7A 172 z Lowercase z
123 0x7B 173 { Left brace
124 0x7C 174 | Vertical bar
125 0x7D 175 } Right brace
126 0x7E 176 ~ Tilde
127 0x7F 177 DEL Delete

 

→ 아스키 함수를 포함하여 공격 구문을 만들어 본다.

SELECT * FROM test_table WHERE 학생이름 = '유라' AND ASCII(SUBSTRING((SELECT database()), 1, 1))>65

DB이름의 첫 글자가 아스키코드 65 초과이냐라고 묻는다.

이렇게 부등호를 이용하여 공격을 효율적으로 할 수 있다.

 

▶Time-based Blind SQL injection

쿼리에 지연시간을 넣어 놓고 반환되는 시간을 살펴봄으로써 쿼리가 실행되는지, 참 또는 거짓인지를 알아낼 수 있다.

다음은 MySQL에서 사용할 수 있는 시간기반 블라인드 SQL 공격 기법이다.

1 AND IF(SUBSTRING((SELECT database()), 1, 1) = 'a', SLEEP(5), 0);

데이터 베이스 첫 글자가 a 이면 5초간 지연 후 결과가 나올 것이다.

 

엥..? 그럼 참, 거짓 기반 보다 시간만 더 소요되고, Boolean-based Blind SQL injection의 하위 호환 아닌가..? 

→ 하지만 엄청난 장점 하나가 있었다.

무려.. 사용자에게 쿼리 결과를 보여주지 않아도 가능한 공격이라는 것이다.

 

관련글 더보기