sqlmap/lib/utils/hash.py
Miroslav Stampar 3c31ccd16e minor update
2011-10-26 22:37:04 +00:00

706 lines
25 KiB
Python

#!/usr/bin/env python
"""
$Id$
Copyright (c) 2006-2011 sqlmap developers (http://www.sqlmap.org/)
See the file 'doc/COPYING' for copying permission
"""
try:
from crypt import crypt
except ImportError, _:
from extra.fcrypt.fcrypt import crypt
_multiprocessing = None
try:
import multiprocessing
# problems on FreeBSD (Reference: http://www.eggheadcafe.com/microsoft/Python/35880259/multiprocessing-on-freebsd.aspx)
_ = multiprocessing.Queue()
except (ImportError, OSError):
pass
else:
_multiprocessing = multiprocessing
import os
import re
import time
from hashlib import md5
from hashlib import sha1
from Queue import Queue
from zipfile import ZipFile
from extra.pydes.pyDes import des
from extra.pydes.pyDes import CBC
from lib.core.common import Backend
from lib.core.common import checkFile
from lib.core.common import clearConsoleLine
from lib.core.common import dataToStdout
from lib.core.common import getCompiledRegex
from lib.core.common import getFileItems
from lib.core.common import getPublicTypeMembers
from lib.core.common import normalizeUnicode
from lib.core.common import paths
from lib.core.common import readInput
from lib.core.common import singleTimeLogMessage
from lib.core.common import singleTimeWarnMessage
from lib.core.common import Wordlist
from lib.core.convert import hexdecode
from lib.core.convert import hexencode
from lib.core.convert import utf8encode
from lib.core.data import kb
from lib.core.data import logger
from lib.core.enums import DBMS
from lib.core.enums import HASH
from lib.core.exception import sqlmapFilePathException
from lib.core.exception import sqlmapUserQuitException
from lib.core.settings import COMMON_PASSWORD_SUFFIXES
from lib.core.settings import DUMMY_USER_PREFIX
from lib.core.settings import GENERAL_IP_ADDRESS_REGEX
from lib.core.settings import HASH_MOD_ITEM_DISPLAY
from lib.core.settings import IS_WIN
from lib.core.settings import PYVERSION
from lib.core.settings import ML
from lib.core.settings import UNICODE_ENCODING
from lib.core.settings import ROTATING_CHARS
def mysql_passwd(password, uppercase=True):
"""
Reference(s):
http://csl.sublevel3.org/mysql-password-function/
>>> mysql_passwd(password='testpass', uppercase=True)
'*00E247AC5F9AF26AE0194B41E1E769DEE1429A29'
"""
retVal = "*%s" % sha1(sha1(password).digest()).hexdigest()
return retVal.upper() if uppercase else retVal.lower()
def mysql_old_passwd(password, uppercase=True): # prior to version '4.1'
"""
Reference(s):
http://www.sfr-fresh.com/unix/privat/tpop3d-1.5.5.tar.gz:a/tpop3d-1.5.5/password.c
http://voidnetwork.org/5ynL0rd/darkc0de/python_script/darkMySQLi.html
>>> mysql_old_passwd(password='testpass', uppercase=True)
'7DCDA0D57290B453'
"""
a, b, c = 1345345333, 7, 0x12345671
for d in password:
if d == ' ' or d == '\t':
continue
e = ord(d)
a ^= (((a & 63) + b) * e) + (a << 8)
c += (c << 8) ^ a
b += e
retVal = "%08lx%08lx" % (a & ((1 << 31) - 1), c & ((1 << 31) - 1))
return retVal.upper() if uppercase else retVal.lower()
def postgres_passwd(password, username, uppercase=False):
"""
Reference(s):
http://pentestmonkey.net/blog/cracking-postgres-hashes/
>>> postgres_passwd(password='testpass', username='testuser', uppercase=False)
'md599e5ea7a6f7c3269995cba3927fd0093'
"""
retVal = "md5%s" % md5(password + username).hexdigest()
return retVal.upper() if uppercase else retVal.lower()
def mssql_passwd(password, salt, uppercase=False):
"""
Reference(s):
http://www.leidecker.info/projects/phrasendrescher/mssql.c
https://www.evilfingers.com/tools/GSAuditor.php
>>> mssql_passwd(password='testpass', salt='4086ceb6', uppercase=False)
'0x01004086ceb60c90646a8ab9889fe3ed8e5c150b5460ece8425a'
"""
binsalt = hexdecode(salt)
unistr = "".join(map(lambda c: ("%s\0" if ord(c) < 256 else "%s") % utf8encode(c), password))
retVal = "0100%s%s" % (salt, sha1(unistr + binsalt).hexdigest())
return "0x%s" % (retVal.upper() if uppercase else retVal.lower())
def mssql_old_passwd(password, salt, uppercase=True): # prior to version '2005'
"""
Reference(s):
www.exploit-db.com/download_pdf/15537/
http://www.leidecker.info/projects/phrasendrescher/mssql.c
https://www.evilfingers.com/tools/GSAuditor.php
>>> mssql_old_passwd(password='testpass', salt='4086ceb6', uppercase=True)
'0x01004086CEB60C90646A8AB9889FE3ED8E5C150B5460ECE8425AC7BB7255C0C81D79AA5D0E93D4BB077FB9A51DA0'
"""
binsalt = hexdecode(salt)
unistr = "".join(map(lambda c: ("%s\0" if ord(c) < 256 else "%s") % utf8encode(c), password))
retVal = "0100%s%s%s" % (salt, sha1(unistr + binsalt).hexdigest(), sha1(unistr.upper() + binsalt).hexdigest())
return "0x%s" % (retVal.upper() if uppercase else retVal.lower())
def oracle_passwd(password, salt, uppercase=True):
"""
Reference(s):
https://www.evilfingers.com/tools/GSAuditor.php
http://www.notesbit.com/index.php/scripts-oracle/oracle-11g-new-password-algorithm-is-revealed-by-seclistsorg/
http://seclists.org/bugtraq/2007/Sep/304
>>> oracle_passwd(password='SHAlala', salt='1B7B5F82B7235E9E182C', uppercase=True)
'S:2BFCFDF5895014EE9BB2B9BA067B01E0389BB5711B7B5F82B7235E9E182C'
"""
binsalt = hexdecode(salt)
retVal="s:%s%s" % (sha1(utf8encode(password) + binsalt).hexdigest(), salt)
return retVal.upper() if uppercase else retVal.lower()
def oracle_old_passwd(password, username, uppercase=True): # prior to version '11g'
"""
Reference(s):
http://www.notesbit.com/index.php/scripts-oracle/oracle-11g-new-password-algorithm-is-revealed-by-seclistsorg/
>>> oracle_old_passwd(password='tiger', username='scott', uppercase=True)
'F894844C34402B67'
"""
IV, pad = "\0"*8, "\0"
if isinstance(username, unicode):
username = unicode.encode(username, UNICODE_ENCODING) #pyDes has issues with unicode strings
unistr = "".join("\0%s" % c for c in (username + password).upper())
cipher = des(hexdecode("0123456789ABCDEF"), CBC, IV, pad)
encrypted = cipher.encrypt(unistr)
cipher = des(encrypted[-8:], CBC, IV, pad)
encrypted = cipher.encrypt(unistr)
retVal = hexencode(encrypted[-8:])
return retVal.upper() if uppercase else retVal.lower()
def md5_generic_passwd(password, uppercase=False):
"""
>>> md5_generic_passwd(password='testpass', uppercase=False)
'179ad45c6ce2cb97cf1029e212046e81'
"""
retVal = md5(password).hexdigest()
return retVal.upper() if uppercase else retVal.lower()
def sha1_generic_passwd(password, uppercase=False):
"""
>>> sha1_generic_passwd(password='testpass', uppercase=False)
'206c80413b9a96c1312cc346b7d2517b84463edd'
"""
retVal = sha1(password).hexdigest()
return retVal.upper() if uppercase else retVal.lower()
def crypt_generic_passwd(password, salt, uppercase=False):
"""
Reference(s):
http://docs.python.org/library/crypt.html
http://helpful.knobs-dials.com/index.php/Hashing_notes
http://php.net/manual/en/function.crypt.php
http://carey.geek.nz/code/python-fcrypt/
>>> crypt_generic_passwd(password='rasmuslerdorf', salt='rl', uppercase=False)
'rl.3StKT.4T8M'
"""
retVal = crypt(password, salt)
return retVal.upper() if uppercase else retVal
__functions__ = {
HASH.MYSQL: mysql_passwd,
HASH.MYSQL_OLD: mysql_old_passwd,
HASH.POSTGRES: postgres_passwd,
HASH.MSSQL: mssql_passwd,
HASH.MSSQL_OLD: mssql_old_passwd,
HASH.ORACLE: oracle_passwd,
HASH.ORACLE_OLD: oracle_old_passwd,
HASH.MD5_GENERIC: md5_generic_passwd,
HASH.SHA1_GENERIC: sha1_generic_passwd,
HASH.CRYPT_GENERIC: crypt_generic_passwd
}
def attackCachedUsersPasswords():
if kb.data.cachedUsersPasswords:
results = dictionaryAttack(kb.data.cachedUsersPasswords)
for (user, hash_, password) in results:
for i in xrange(len(kb.data.cachedUsersPasswords[user])):
if kb.data.cachedUsersPasswords[user][i] and hash_.lower() in kb.data.cachedUsersPasswords[user][i].lower()\
and 'clear-text password' not in kb.data.cachedUsersPasswords[user][i].lower():
kb.data.cachedUsersPasswords[user][i] += "%s clear-text password: %s" % ('\n' if kb.data.cachedUsersPasswords[user][i][-1] != '\n' else '', password)
def attackDumpedTable():
if kb.data.dumpedTable:
infoMsg = "analyzing table dump for possible password hashes"
logger.info(infoMsg)
table = kb.data.dumpedTable
columns = table.keys()
count = table["__infos__"]["count"]
colUser = ''
colPasswords = set()
attack_dict = {}
for column in columns:
if column and column.lower() in ('user', 'username', 'user_name'):
colUser = column
break
for i in xrange(count):
for column in columns:
if column == colUser or column == '__infos__':
continue
if len(table[column]['values']) <= i:
continue
value = table[column]['values'][i]
if hashRecognition(value):
if colUser and i < len(table[colUser]['values']):
if table[colUser]['values'][i] not in attack_dict:
attack_dict[table[colUser]['values'][i]] = []
attack_dict[table[colUser]['values'][i]].append(value)
else:
attack_dict['%s%d' % (DUMMY_USER_PREFIX, i)] = [value]
colPasswords.add(column)
if attack_dict:
message = "recognized possible password hashes in column%s " % ("s" if len(colPasswords) > 1 else "")
message += "'%s'. Do you want to " % ", ".join(col for col in colPasswords)
message += "crack them via a dictionary-based attack? [Y/n/q]"
test = readInput(message, default="Y")
if test[0] in ("n", "N"):
return
elif test[0] in ("q", "Q"):
raise sqlmapUserQuitException
results = dictionaryAttack(attack_dict)
for (_, hash_, password) in results:
if not hash_:
continue
for i in xrange(count):
for column in columns:
if not (column == colUser or column == '__infos__' or len(table[column]['values']) <= i):
value = table[column]['values'][i]
if value and value.lower() == hash_.lower():
table[column]['values'][i] += " (%s)" % password
table[column]['length'] = max(table[column]['length'], len(table[column]['values'][i]))
def hashRecognition(value):
retVal = None
isOracle, isMySQL = Backend.isDbms(DBMS.ORACLE), Backend.isDbms(DBMS.MYSQL)
if isinstance(value, basestring):
for name, regex in getPublicTypeMembers(HASH):
# Hashes for Oracle and old MySQL look the same hence these checks
if isOracle and regex == HASH.MYSQL_OLD:
continue
elif isMySQL and regex == HASH.ORACLE_OLD:
continue
elif regex == HASH.CRYPT_GENERIC:
if any([getCompiledRegex(GENERAL_IP_ADDRESS_REGEX).match(value), value.lower() == value, value.upper() == value, value.isdigit()]):
continue
elif getCompiledRegex(regex).match(value):
retVal = regex
break
return retVal
def __bruteProcessVariantA(attack_info, hash_regex, wordlist, suffix, retVal, proc_id, proc_count):
count = 0
rotator = 0
try:
for word in wordlist:
if not attack_info:
break
if not isinstance(word, basestring):
continue
if suffix:
word = word + suffix
try:
current = __functions__[hash_regex](password = word, uppercase = False)
for item in list(attack_info):
((user, hash_), _) = item
count += 1
if hash_ == current:
retVal.put((user, hash_, word))
clearConsoleLine()
infoMsg = "\r[%s] [INFO] cracked password '%s'" % (time.strftime("%X"), word)
if user and not user.startswith(DUMMY_USER_PREFIX):
infoMsg += " for user '%s'\n" % user
else:
infoMsg += " for hash '%s'\n" % hash_
dataToStdout(infoMsg, True)
attack_info.remove(item)
elif proc_id == 0 and count % HASH_MOD_ITEM_DISPLAY == 0 or hash_regex in (HASH.ORACLE_OLD) or hash_regex == HASH.CRYPT_GENERIC and IS_WIN:
rotator += 1
if rotator >= len(ROTATING_CHARS):
rotator = 0
status = 'current status: %s... %s' % (word.ljust(5)[:5], ROTATING_CHARS[rotator])
dataToStdout("\r[%s] [INFO] %s" % (time.strftime("%X"), status))
except KeyboardInterrupt:
raise
except Exception, msg:
print msg
warnMsg = "there was a problem while hashing entry: %s. " % repr(word)
warnMsg += "Please report by e-mail to %s" % ML
logger.critical(warnMsg)
except KeyboardInterrupt:
pass
def __bruteProcessVariantB(user, hash_, kwargs, hash_regex, wordlist, suffix, retVal, found, proc_id, proc_count):
count = 0
rotator = 0
try:
for word in wordlist:
if found.value:
break
current = __functions__[hash_regex](password = word, uppercase = False, **kwargs)
count += 1
if not isinstance(word, basestring):
continue
if suffix:
word = word + suffix
try:
if hash_ == current:
if hash_regex == HASH.ORACLE_OLD: #only for cosmetic purposes
word = word.upper()
retVal.put((user, hash_, word))
clearConsoleLine()
infoMsg = "\r[%s] [INFO] cracked password '%s'" % (time.strftime("%X"), word)
if user and not user.startswith(DUMMY_USER_PREFIX):
infoMsg += " for user '%s'\n" % user
else:
infoMsg += " for hash '%s'\n" % hash_
dataToStdout(infoMsg, True)
found.value = True
elif proc_id == 0 and count % HASH_MOD_ITEM_DISPLAY == 0 or hash_regex in (HASH.ORACLE_OLD) or hash_regex == HASH.CRYPT_GENERIC and IS_WIN:
rotator += 1
if rotator >= len(ROTATING_CHARS):
rotator = 0
status = 'current status: %s... %s' % (word.ljust(5)[:5], ROTATING_CHARS[rotator])
if not user.startswith(DUMMY_USER_PREFIX):
status += ' (user: %s)' % user
dataToStdout("\r[%s] [INFO] %s" % (time.strftime("%X"), status))
except KeyboardInterrupt:
raise
except:
warnMsg = "there was a problem while hashing entry: %s. " % repr(word)
warnMsg += "Please report by e-mail to %s" % ML
logger.critical(warnMsg)
except KeyboardInterrupt:
pass
def dictionaryAttack(attack_dict):
suffix_list = [""]
hash_regexes = []
results = []
processException = False
for (_, hashes) in attack_dict.items():
for hash_ in hashes:
if not hash_:
continue
hash_ = hash_.split()[0]
regex = hashRecognition(hash_)
if regex and regex not in hash_regexes:
hash_regexes.append(regex)
infoMsg = "using hash method '%s'" % __functions__[regex].func_name
logger.info(infoMsg)
for hash_regex in hash_regexes:
keys = set()
attack_info = []
for (user, hashes) in attack_dict.items():
for hash_ in hashes:
if not hash_:
continue
hash_ = hash_.split()[0].lower()
if getCompiledRegex(hash_regex).match(hash_):
item = None
if hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC):
item = [(user, hash_), {}]
elif hash_regex in (HASH.ORACLE_OLD, HASH.POSTGRES):
item = [(user, hash_), {'username': user}]
elif hash_regex in (HASH.ORACLE):
item = [(user, hash_), {'salt': hash_[-20:]}]
elif hash_regex in (HASH.MSSQL, HASH.MSSQL_OLD):
item = [(user, hash_), {'salt': hash_[6:14]}]
elif hash_regex in (HASH.CRYPT_GENERIC):
item = [(user, hash_), {'salt': hash_[0:2]}]
key = hash(repr(item))
if item and key not in keys:
attack_info.append(item)
keys.add(key)
if not attack_info:
continue
if not kb.wordlist:
while not kb.wordlist:
message = "what dictionary do you want to use?\n"
message += "[1] default dictionary file (press Enter)\n"
message += "[2] custom dictionary file\n"
message += "[3] file with list of dictionary files"
choice = readInput(message, default="1")
try:
if choice == "2":
message = "what's the custom dictionary's location?\n"
dictPaths = [readInput(message)]
logger.info("using custom dictionary")
elif choice == "3":
message = "what's the list file location?\n"
listPath = readInput(message)
checkFile(listPath)
dictPaths = getFileItems(listPath)
logger.info("using custom list of dictionaries")
else:
# It is the slowest of all methods hence smaller default dict
if hash_regex == HASH.ORACLE_OLD:
dictPaths = [paths.ORACLE_DEFAULT_PASSWD]
else:
dictPaths = [paths.WORDLIST]
logger.info("using default dictionary")
for dictPath in dictPaths:
checkFile(dictPath)
kb.wordlist = Wordlist(dictPaths)
if _multiprocessing:
kb.wordlist.lock = _multiprocessing.Lock()
except sqlmapFilePathException, msg:
warnMsg = "there was a problem while loading dictionaries"
warnMsg += " ('%s')" % msg
logger.critical(warnMsg)
message = "do you want to use common password suffixes? (slow!) [y/N] "
test = readInput(message, default="N")
if test[0] in ("y", "Y"):
suffix_list += COMMON_PASSWORD_SUFFIXES
infoMsg = "starting dictionary-based cracking (%s)" % __functions__[hash_regex].func_name
logger.info(infoMsg)
for item in attack_info:
((user, _), _) = item
if user and not user.startswith(DUMMY_USER_PREFIX):
kb.wordlist.append(normalizeUnicode(user))
if hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC):
for suffix in suffix_list:
if len(attack_info) == len(results) or processException:
break
if suffix:
clearConsoleLine()
infoMsg = "using suffix '%s'" % suffix
logger.info(infoMsg)
kb.wordlist.rewind()
retVal = None
processes = []
try:
if _multiprocessing and not IS_WIN:
if _multiprocessing.cpu_count() > 1:
infoMsg = "starting %d processes " % _multiprocessing.cpu_count()
singleTimeLogMessage(infoMsg)
retVal = _multiprocessing.Queue()
for i in xrange(_multiprocessing.cpu_count()):
p = _multiprocessing.Process(target=__bruteProcessVariantA, args=(attack_info, hash_regex, kb.wordlist, suffix, retVal, i, _multiprocessing.cpu_count()))
processes.append(p)
for p in processes:
p.start()
for p in processes:
p.join()
else:
warnMsg = "multiprocessing hash cracking is currently "
warnMsg += "not supported on this platform"
singleTimeWarnMessage(warnMsg)
retVal = Queue()
__bruteProcessVariantA(attack_info, hash_regex, kb.wordlist, suffix, retVal, 0, 1)
except KeyboardInterrupt:
print
processException = True
warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)"
logger.warn(warnMsg)
for process in processes:
process.terminate()
process.join()
while not retVal.empty():
results.append(retVal.get(block=False))
clearConsoleLine()
else:
for ((user, hash_), kwargs) in attack_info:
if processException:
break
count = 0
found = False
for suffix in suffix_list:
if found or processException:
break
if suffix:
clearConsoleLine()
infoMsg = "using suffix '%s'" % suffix
logger.info(infoMsg)
kb.wordlist.rewind()
retVal = None
processes = []
try:
if _multiprocessing and not IS_WIN:
if _multiprocessing.cpu_count() > 1:
infoMsg = "starting %d processes " % _multiprocessing.cpu_count()
singleTimeLogMessage(infoMsg)
retVal = _multiprocessing.Queue()
found_ = _multiprocessing.Value('i', False)
for i in xrange(_multiprocessing.cpu_count()):
p = _multiprocessing.Process(target=__bruteProcessVariantB, args=(user, hash_, kwargs, hash_regex, kb.wordlist, suffix, retVal, found_, i, _multiprocessing.cpu_count()))
processes.append(p)
for p in processes:
p.start()
for p in processes:
p.join()
found = found_.value != 0
else:
warnMsg = "multiprocessing hash cracking is currently "
warnMsg += "not supported on this platform"
singleTimeWarnMessage(warnMsg)
class Value():
pass
retVal = Queue()
found_ = Value()
found_.value = False
__bruteProcessVariantB(user, hash_, kwargs, hash_regex, kb.wordlist, suffix, retVal, found_, 0, 1)
found = found_.value
except KeyboardInterrupt:
print
processException = True
warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)"
logger.warn(warnMsg)
for process in processes:
process.terminate()
process.join()
while not retVal.empty():
results.append(retVal.get(block=False))
clearConsoleLine()
if len(hash_regexes) == 0:
warnMsg = "unknown hash format. "
warnMsg += "Please report by e-mail to %s" % ML
logger.warn(warnMsg)
if len(results) == 0:
warnMsg = "no clear password(s) found"
logger.warn(warnMsg)
return results