diff --git a/lib/core/enums.py b/lib/core/enums.py
index dfee64c5f..ceeb87ab7 100644
--- a/lib/core/enums.py
+++ b/lib/core/enums.py
@@ -73,6 +73,7 @@ class HASH:
MD5_GENERIC = r'(?i)\A[0-9a-f]{32}\Z'
SHA1_GENERIC = r'(?i)\A[0-9a-f]{40}\Z'
CRYPT_GENERIC = r'(?i)\A[./0-9A-Za-z]{13}\Z'
+ WORDPRESS = r'(?i)\A\$P\$[./0-9A-Za-z]{31}\Z'
# Reference: http://www.zytrax.com/tech/web/mobile_ids.html
class MOBILES:
diff --git a/lib/core/settings.py b/lib/core/settings.py
index 37bfbc22f..8673ba30a 100644
--- a/lib/core/settings.py
+++ b/lib/core/settings.py
@@ -365,6 +365,9 @@ REFLECTIVE_MISS_THRESHOLD = 20
# Regular expression used for extracting HTML title
HTML_TITLE_REGEX = "
(?P[^<]+)"
+# Table used for Base64 conversion in WordPress hash cracking routine
+ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
+
# Chars used to quickly distinguish if the user provided tainted parameter values
DUMMY_SQL_INJECTION_CHARS = ";()'"
@@ -402,4 +405,7 @@ PARAMETER_SPLITTING_REGEX = r'[,|;]'
UNION_CHAR_REGEX = r'\A\w+\Z'
# Attribute used for storing original parameter value in special cases (e.g. POST)
-UNENCODED_ORIGINAL_VALUE = 'original'
\ No newline at end of file
+UNENCODED_ORIGINAL_VALUE = 'original'
+
+# Common column names containing usernames (used for hash cracking in some cases)
+COMMON_USER_COLUMNS = ('user', 'username', 'user_name', 'benutzername', 'benutzer', 'utilisateur', 'usager', 'consommateur', 'utente', 'utilizzatore', 'usufrutuario', 'korisnik', 'usuario', 'consumidor')
\ No newline at end of file
diff --git a/lib/utils/hash.py b/lib/utils/hash.py
index 0aa914dee..471f0db59 100644
--- a/lib/utils/hash.py
+++ b/lib/utils/hash.py
@@ -58,10 +58,12 @@ 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 COMMON_USER_COLUMNS
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 ITOA64
from lib.core.settings import PYVERSION
from lib.core.settings import ML
from lib.core.settings import UNICODE_ENCODING
@@ -214,6 +216,7 @@ def sha1_generic_passwd(password, uppercase=False):
return retVal.upper() if uppercase else retVal.lower()
+
def crypt_generic_passwd(password, salt, uppercase=False):
"""
Reference(s):
@@ -230,6 +233,60 @@ def crypt_generic_passwd(password, salt, uppercase=False):
return retVal.upper() if uppercase else retVal
+def wordpress_passwd(password, salt, count, prefix, uppercase=False):
+ """
+ Reference(s):
+ http://packetstormsecurity.org/files/74448/phpassbrute.py.txt
+ http://scriptserver.mainframe8.com/wordpress_password_hasher.php
+
+ >>> wordpress_passwd(password='testpass', salt='dYPSjeF4', count=2048)
+ ''
+ """
+
+ def _encode64(input_, count):
+ output = ''
+ i = 0
+
+ while i < count:
+ value = ord(input_[i])
+ i += 1
+ output = output + ITOA64[value & 0x3f]
+
+ if i < count:
+ value = value | (ord(input_[i]) << 8)
+
+ output = output + ITOA64[(value>>6) & 0x3f]
+
+ i += 1
+ if i >= count:
+ break
+
+ if i < count:
+ value = value | (ord(input_[i]) << 16)
+
+ output = output + ITOA64[(value>>12) & 0x3f]
+
+ i += 1
+ if i >= count:
+ break
+
+ output = output + ITOA64[(value>>18) & 0x3f]
+
+ return output
+
+ cipher = md5(salt)
+ cipher.update(password)
+ hash_ = cipher.digest()
+
+ for i in xrange(count):
+ _ = md5(hash_)
+ _.update(password)
+ hash_ = _.digest()
+
+ retVal = prefix + _encode64(hash_, 16)
+
+ return retVal.upper() if uppercase else retVal
+
__functions__ = {
HASH.MYSQL: mysql_passwd,
HASH.MYSQL_OLD: mysql_old_passwd,
@@ -240,7 +297,8 @@ __functions__ = {
HASH.ORACLE_OLD: oracle_old_passwd,
HASH.MD5_GENERIC: md5_generic_passwd,
HASH.SHA1_GENERIC: sha1_generic_passwd,
- HASH.CRYPT_GENERIC: crypt_generic_passwd
+ HASH.CRYPT_GENERIC: crypt_generic_passwd,
+ HASH.WORDPRESS: wordpress_passwd
}
def attackCachedUsersPasswords():
@@ -268,7 +326,7 @@ def attackDumpedTable():
attack_dict = {}
for column in columns:
- if column and column.lower() in ('user', 'username', 'user_name'):
+ if column and column.lower() in COMMON_USER_COLUMNS:
colUser = column
break
@@ -385,7 +443,7 @@ def __bruteProcessVariantA(attack_info, hash_regex, wordlist, suffix, retVal, pr
attack_info.remove(item)
- elif proc_id == 0 and count % HASH_MOD_ITEM_DISPLAY == 0 or hash_regex == HASH.ORACLE_OLD or hash_regex == HASH.CRYPT_GENERIC and IS_WIN:
+ elif (proc_id == 0 or getattr(proc_count, 'value', 0) == 1) and count % HASH_MOD_ITEM_DISPLAY == 0 or hash_regex == HASH.ORACLE_OLD or hash_regex == HASH.CRYPT_GENERIC and IS_WIN:
rotator += 1
if rotator >= len(ROTATING_CHARS):
rotator = 0
@@ -404,6 +462,10 @@ def __bruteProcessVariantA(attack_info, hash_regex, wordlist, suffix, retVal, pr
except KeyboardInterrupt:
pass
+ finally:
+ if hasattr(proc_count, 'value'):
+ proc_count.value -= 1
+
def __bruteProcessVariantB(user, hash_, kwargs, hash_regex, wordlist, suffix, retVal, found, proc_id, proc_count):
count = 0
rotator = 0
@@ -441,7 +503,8 @@ def __bruteProcessVariantB(user, hash_, kwargs, hash_regex, wordlist, suffix, re
dataToStdout(infoMsg, True)
found.value = True
- elif proc_id == 0 and count % HASH_MOD_ITEM_DISPLAY == 0 or hash_regex == HASH.ORACLE_OLD or hash_regex == HASH.CRYPT_GENERIC and IS_WIN:
+
+ elif (proc_id == 0 or getattr(proc_count, 'value', 0) == 1) and count % HASH_MOD_ITEM_DISPLAY == 0 or hash_regex == HASH.ORACLE_OLD or hash_regex == HASH.CRYPT_GENERIC and IS_WIN:
rotator += 1
if rotator >= len(ROTATING_CHARS):
rotator = 0
@@ -461,6 +524,9 @@ def __bruteProcessVariantB(user, hash_, kwargs, hash_regex, wordlist, suffix, re
except KeyboardInterrupt:
pass
+ finally:
+ if hasattr(proc_count, 'value'):
+ proc_count.value -= 1
def dictionaryAttack(attack_dict):
suffix_list = [""]
@@ -491,11 +557,14 @@ def dictionaryAttack(attack_dict):
if not hash_:
continue
- hash_ = hash_.split()[0].lower()
+ hash_ = hash_.split()[0]
if getCompiledRegex(hash_regex).match(hash_):
item = None
+ if hash_regex not in (HASH.CRYPT_GENERIC, HASH.WORDPRESS):
+ hash_ = hash_.lower()
+
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):
@@ -506,6 +575,8 @@ def dictionaryAttack(attack_dict):
item = [(user, hash_), {'salt': hash_[6:14]}]
elif hash_regex in (HASH.CRYPT_GENERIC):
item = [(user, hash_), {'salt': hash_[0:2]}]
+ elif hash_regex in (HASH.WORDPRESS):
+ item = [(user, hash_), {'salt': hash_[4:12], 'count': 1<