[보안 공부] 7주 차 - Error Based, Blind SQL Injection
제가 사용하는 DB는 MySQL이랑 MariaDB입니다. 다른 DB에서는 방식이 조금 다를 수 있습니다.
여기서는 개념 위주 설명이고, 자세한 공격기법은 CTF 문제에서 사용하겠습니다.
▶Error Based SQL injection
→ 에러기반 SQL 인젝션의 핵심은 아래와 같다.
→ 왜 이렇게 복잡하게 공격하느냐..?
→ 지난 시간 무적의 LIKE와, UNION SELECT 구문을 이용하면 DB를 쉽게 털 수 있지 않은가?
→ 대표적으로 SQL쿼리 결과를 직접적으로 보여주지 않고, 그런 값이 존재합니다 or 존재하지 않습니다 정도만 알려주는 경우, UNION을 써봤자 소용이 없다.
→ 아래 더 자세한 이유 참조.
▶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() 함수는 주어진 숫자보다 작거나 같은 최대 정수를 반환하는 함수이다.
→ RAND() 함수는 MySQL에서 난수를 생성하는 함수이다. 인자 값을 주지 않으면 0과 1 사이의 무작위 실수를 반환한다. 인자로 0을 주게 되면, 시드값 0을 이용하여 예측 가능한 난수를 생성한다. (해보니 시드가 같으면 같은 값이 나온다.)
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 작동 때문에 에러를 출력한다. 랜덤 함수가 들어가 있어서 한번씩은 정상 실행되기도 한다.
→ 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표현식)
→ 아래 문구로 내 DB에 공격을 해본다.
SELECT * FROM `test_table` WHERE 학생이름='유라' and EXTRACTVALUE(NULL, CONCAT('!', (SELECT database()), ']'));
→ XPATH 문법 에러를 띄웠고, 이번에도 순순히 DB이름을 내주었다.
→ 이 에러메시지가 사용자에게 표시되면 공격을 당하는 것이다.
→ 마찬가지로 SELECT database() 대신 원하는 쿼리를 넣으면 된다.
★생각해 보기
→ 가만히 생각해 보니, 함수에서 경로를 표현하는 부분에서 SQL이 실행되었음을 볼 수 있다.
→ 그렇다면 왜? 경로를 문자열 정로도로 처리하지 않고 명령어처럼 실행, 해석하여 이런 문제를 발생시키는지 알아보았다.
→ 동적 XPath 표현식이 문제였다. EXTRACTVALUE와 UPDATEXML 함수는 동적 XPath 표현식을 처리할 때 SQL과 같은 명령을 포함할 수 있었다.
→ 그렇다면 동적 XPath 표현식을 사용하지 못하도록 방어하는 방법은 없을까 알아보았다.
→ 입력 검증(화이트리스트, 블랙리스트 등), 준비된 문장사용, XML 파서 사용 하는 방법들이 있었다.
▶Error Based SQL injection에 사용되는 여러 함수들 (현재는 있다는 것만 보고 넘어가자)
→ MySQL
→ Microsoft SQL Server
→ Oracle
→ PostgreSQL
▶Blind SQL injection
→ 이름만 봐도 고생할 것 같은 느낌이다.
→ 순서를 봐서 알겠지만 Error based SQLi가 안되면 쓰는 방법이기도 하다.
→ 블라인드 SQL인젝션에는 크게 두 가지가 있다.
▶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부터 시작임에 유의)
→ 결국 해당 공격은 데이터베이스 이름의 첫 글자가 a이냐 아니냐를 묻고 있는 것이다.
→ SELECT database() 대신 원하는 공격 구문을 넣으면 된다.
→ ASCII() 함수는 아스키코드로 변환하는 함수이다.
→ 아래는 아스키코드표이다. (128개)
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의 하위 호환 아닌가..?
→ 하지만 엄청난 장점 하나가 있었다.
→ 무려.. 사용자에게 쿼리 결과를 보여주지 않아도 가능한 공격이라는 것이다.
[보안 공부] 9주 차 - XSS(크로스 사이트 스크립팅), 보고서 작성 (0) | 2024.06.19 |
---|---|
[보안 공부] 8주 차 - SQLi Point, SQLi 정리 (0) | 2024.06.12 |
[보안 공부] 6주 차 - SQL Injection 생각해 보기 (0) | 2024.05.29 |
[보안 공부] 5주 차 - 게시판 파일 업로드 구현 (0) | 2024.05.22 |
[보안 공부] 4주 차 - Burp Suite, 게시판 구현, 과제 (0) | 2024.05.14 |