Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e1a92d5

Browse files
committed
implementing WordPress phpass hash cracking routine
1 parent f197993 commit e1a92d5

4 files changed

Lines changed: 91 additions & 36 deletions

File tree

lib/core/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class HASH:
7373
MD5_GENERIC = r'(?i)\A[0-9a-f]{32}\Z'
7474
SHA1_GENERIC = r'(?i)\A[0-9a-f]{40}\Z'
7575
CRYPT_GENERIC = r'(?i)\A[./0-9A-Za-z]{13}\Z'
76+
WORDPRESS = r'(?i)\A\$P\$[./0-9A-Za-z]{31}\Z'
7677

7778
# Reference: http://www.zytrax.com/tech/web/mobile_ids.html
7879
class MOBILES:

lib/core/settings.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,9 @@
365365
# Regular expression used for extracting HTML title
366366
HTML_TITLE_REGEX = "<title>(?P<result>[^<]+)</title>"
367367

368+
# Table used for Base64 conversion in WordPress hash cracking routine
369+
ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
370+
368371
# Chars used to quickly distinguish if the user provided tainted parameter values
369372
DUMMY_SQL_INJECTION_CHARS = ";()'"
370373

@@ -402,4 +405,7 @@
402405
UNION_CHAR_REGEX = r'\A\w+\Z'
403406

404407
# Attribute used for storing original parameter value in special cases (e.g. POST)
405-
UNENCODED_ORIGINAL_VALUE = 'original'
408+
UNENCODED_ORIGINAL_VALUE = 'original'
409+
410+
# Common column names containing usernames (used for hash cracking in some cases)
411+
COMMON_USER_COLUMNS = ('user', 'username', 'user_name', 'benutzername', 'benutzer', 'utilisateur', 'usager', 'consommateur', 'utente', 'utilizzatore', 'usufrutuario', 'korisnik', 'usuario', 'consumidor')

lib/utils/hash.py

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,12 @@
5858
from lib.core.exception import sqlmapFilePathException
5959
from lib.core.exception import sqlmapUserQuitException
6060
from lib.core.settings import COMMON_PASSWORD_SUFFIXES
61+
from lib.core.settings import COMMON_USER_COLUMNS
6162
from lib.core.settings import DUMMY_USER_PREFIX
6263
from lib.core.settings import GENERAL_IP_ADDRESS_REGEX
6364
from lib.core.settings import HASH_MOD_ITEM_DISPLAY
6465
from lib.core.settings import IS_WIN
66+
from lib.core.settings import ITOA64
6567
from lib.core.settings import PYVERSION
6668
from lib.core.settings import ML
6769
from lib.core.settings import UNICODE_ENCODING
@@ -214,6 +216,7 @@ def sha1_generic_passwd(password, uppercase=False):
214216

215217
return retVal.upper() if uppercase else retVal.lower()
216218

219+
217220
def crypt_generic_passwd(password, salt, uppercase=False):
218221
"""
219222
Reference(s):
@@ -230,6 +233,60 @@ def crypt_generic_passwd(password, salt, uppercase=False):
230233

231234
return retVal.upper() if uppercase else retVal
232235

236+
def wordpress_passwd(password, salt, count, prefix, uppercase=False):
237+
"""
238+
Reference(s):
239+
http://packetstormsecurity.org/files/74448/phpassbrute.py.txt
240+
http://scriptserver.mainframe8.com/wordpress_password_hasher.php
241+
242+
>>> wordpress_passwd(password='testpass', salt='dYPSjeF4', count=2048)
243+
''
244+
"""
245+
246+
def _encode64(input_, count):
247+
output = ''
248+
i = 0
249+
250+
while i < count:
251+
value = ord(input_[i])
252+
i += 1
253+
output = output + ITOA64[value & 0x3f]
254+
255+
if i < count:
256+
value = value | (ord(input_[i]) << 8)
257+
258+
output = output + ITOA64[(value>>6) & 0x3f]
259+
260+
i += 1
261+
if i >= count:
262+
break
263+
264+
if i < count:
265+
value = value | (ord(input_[i]) << 16)
266+
267+
output = output + ITOA64[(value>>12) & 0x3f]
268+
269+
i += 1
270+
if i >= count:
271+
break
272+
273+
output = output + ITOA64[(value>>18) & 0x3f]
274+
275+
return output
276+
277+
cipher = md5(salt)
278+
cipher.update(password)
279+
hash_ = cipher.digest()
280+
281+
for i in xrange(count):
282+
_ = md5(hash_)
283+
_.update(password)
284+
hash_ = _.digest()
285+
286+
retVal = prefix + _encode64(hash_, 16)
287+
288+
return retVal.upper() if uppercase else retVal
289+
233290
__functions__ = {
234291
HASH.MYSQL: mysql_passwd,
235292
HASH.MYSQL_OLD: mysql_old_passwd,
@@ -240,7 +297,8 @@ def crypt_generic_passwd(password, salt, uppercase=False):
240297
HASH.ORACLE_OLD: oracle_old_passwd,
241298
HASH.MD5_GENERIC: md5_generic_passwd,
242299
HASH.SHA1_GENERIC: sha1_generic_passwd,
243-
HASH.CRYPT_GENERIC: crypt_generic_passwd
300+
HASH.CRYPT_GENERIC: crypt_generic_passwd,
301+
HASH.WORDPRESS: wordpress_passwd
244302
}
245303

246304
def attackCachedUsersPasswords():
@@ -268,7 +326,7 @@ def attackDumpedTable():
268326
attack_dict = {}
269327

270328
for column in columns:
271-
if column and column.lower() in ('user', 'username', 'user_name'):
329+
if column and column.lower() in COMMON_USER_COLUMNS:
272330
colUser = column
273331
break
274332

@@ -385,7 +443,7 @@ def __bruteProcessVariantA(attack_info, hash_regex, wordlist, suffix, retVal, pr
385443

386444
attack_info.remove(item)
387445

388-
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:
446+
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:
389447
rotator += 1
390448
if rotator >= len(ROTATING_CHARS):
391449
rotator = 0
@@ -404,6 +462,10 @@ def __bruteProcessVariantA(attack_info, hash_regex, wordlist, suffix, retVal, pr
404462
except KeyboardInterrupt:
405463
pass
406464

465+
finally:
466+
if hasattr(proc_count, 'value'):
467+
proc_count.value -= 1
468+
407469
def __bruteProcessVariantB(user, hash_, kwargs, hash_regex, wordlist, suffix, retVal, found, proc_id, proc_count):
408470
count = 0
409471
rotator = 0
@@ -441,7 +503,8 @@ def __bruteProcessVariantB(user, hash_, kwargs, hash_regex, wordlist, suffix, re
441503
dataToStdout(infoMsg, True)
442504

443505
found.value = True
444-
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:
506+
507+
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:
445508
rotator += 1
446509
if rotator >= len(ROTATING_CHARS):
447510
rotator = 0
@@ -461,6 +524,9 @@ def __bruteProcessVariantB(user, hash_, kwargs, hash_regex, wordlist, suffix, re
461524
except KeyboardInterrupt:
462525
pass
463526

527+
finally:
528+
if hasattr(proc_count, 'value'):
529+
proc_count.value -= 1
464530

465531
def dictionaryAttack(attack_dict):
466532
suffix_list = [""]
@@ -491,11 +557,14 @@ def dictionaryAttack(attack_dict):
491557
if not hash_:
492558
continue
493559

494-
hash_ = hash_.split()[0].lower()
560+
hash_ = hash_.split()[0]
495561

496562
if getCompiledRegex(hash_regex).match(hash_):
497563
item = None
498564

565+
if hash_regex not in (HASH.CRYPT_GENERIC, HASH.WORDPRESS):
566+
hash_ = hash_.lower()
567+
499568
if hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC):
500569
item = [(user, hash_), {}]
501570
elif hash_regex in (HASH.ORACLE_OLD, HASH.POSTGRES):
@@ -506,6 +575,8 @@ def dictionaryAttack(attack_dict):
506575
item = [(user, hash_), {'salt': hash_[6:14]}]
507576
elif hash_regex in (HASH.CRYPT_GENERIC):
508577
item = [(user, hash_), {'salt': hash_[0:2]}]
578+
elif hash_regex in (HASH.WORDPRESS):
579+
item = [(user, hash_), {'salt': hash_[4:12], 'count': 1<<ITOA64.index(hash_[3]), 'prefix': hash_[:12]}]
509580

510581
if item and hash_ not in keys:
511582
resumed = conf.hashDB.retrieve(hash_)
@@ -545,7 +616,7 @@ def dictionaryAttack(attack_dict):
545616
logger.info("using custom list of dictionaries")
546617
else:
547618
# It is the slowest of all methods hence smaller default dict
548-
if hash_regex == HASH.ORACLE_OLD:
619+
if hash_regex in (HASH.ORACLE_OLD, HASH.WORDPRESS):
549620
dictPaths = [paths.SMALL_DICT]
550621
else:
551622
dictPaths = [paths.WORDLIST]
@@ -602,8 +673,10 @@ def dictionaryAttack(attack_dict):
602673
singleTimeLogMessage(infoMsg)
603674

604675
retVal = _multiprocessing.Queue()
676+
count = _multiprocessing.Value('i', _multiprocessing.cpu_count())
677+
605678
for i in xrange(_multiprocessing.cpu_count()):
606-
p = _multiprocessing.Process(target=__bruteProcessVariantA, args=(attack_info, hash_regex, kb.wordlist, suffix, retVal, i, _multiprocessing.cpu_count()))
679+
p = _multiprocessing.Process(target=__bruteProcessVariantA, args=(attack_info, hash_regex, kb.wordlist, suffix, retVal, i, count))
607680
processes.append(p)
608681

609682
for p in processes:
@@ -671,9 +744,10 @@ def dictionaryAttack(attack_dict):
671744

672745
retVal = _multiprocessing.Queue()
673746
found_ = _multiprocessing.Value('i', False)
747+
count = _multiprocessing.Value('i', _multiprocessing.cpu_count())
674748

675749
for i in xrange(_multiprocessing.cpu_count()):
676-
p = _multiprocessing.Process(target=__bruteProcessVariantB, args=(user, hash_, kwargs, hash_regex, kb.wordlist, suffix, retVal, found_, i, _multiprocessing.cpu_count()))
750+
p = _multiprocessing.Process(target=__bruteProcessVariantB, args=(user, hash_, kwargs, hash_regex, kb.wordlist, suffix, retVal, found_, i, count))
677751
processes.append(p)
678752

679753
for p in processes:

0 commit comments

Comments
 (0)