From 0094f02fb0361eba8b58a262b287a1160099e71b Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 1 Jul 2020 12:46:26 +0200 Subject: [PATCH] Adding support for generic phpass (Wordpress, Drupal, PHPBB3, etc.) (Issue #4252) --- lib/core/enums.py | 2 +- lib/core/settings.py | 2 +- lib/utils/hash.py | 33 +++++++++++++++++++++++---------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/core/enums.py b/lib/core/enums.py index 36784c999..16039fb66 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -177,7 +177,7 @@ class HASH(object): SHA512_GENERIC = r'(?i)\A(0x)?[0-9a-f]{128}\Z' CRYPT_GENERIC = r'\A(?!\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z)(?![0-9]+\Z)[./0-9A-Za-z]{13}\Z' JOOMLA = r'\A[0-9a-f]{32}:\w{32}\Z' - WORDPRESS = r'\A\$P\$[./0-9a-zA-Z]{31}\Z' + PHPASS = r'\A\$[PHQS]\$[./0-9a-zA-Z]{31}\Z' APACHE_MD5_CRYPT = r'\A\$apr1\$.{1,8}\$[./a-zA-Z0-9]+\Z' UNIX_MD5_CRYPT = r'\A\$1\$.{1,8}\$[./a-zA-Z0-9]+\Z' APACHE_SHA1 = r'\A\{SHA\}[a-zA-Z0-9+/]+={0,2}\Z' diff --git a/lib/core/settings.py b/lib/core/settings.py index b88bad86e..35e86dca2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -18,7 +18,7 @@ from lib.core.enums import OS from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.4.7.0" +VERSION = "1.4.7.1" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/utils/hash.py b/lib/utils/hash.py index fc9d80177..b13623dab 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -33,6 +33,7 @@ else: import base64 import binascii import gc +import math import os import re import tempfile @@ -481,14 +482,20 @@ def vbulletin_passwd(password, salt, **kwargs): return "%s:%s" % (md5(binascii.hexlify(md5(getBytes(password)).digest()) + getBytes(salt)).hexdigest(), salt) -def wordpress_passwd(password, salt, count, prefix, **kwargs): +def phpass_passwd(password, salt, count, prefix, **kwargs): """ Reference(s): https://web.archive.org/web/20120219120128/packetstormsecurity.org/files/74448/phpassbrute.py.txt http://scriptserver.mainframe8.com/wordpress_password_hasher.php + https://www.openwall.com/phpass/ + https://github.com/jedie/django-phpBB3/blob/master/django_phpBB3/hashers.py - >>> wordpress_passwd(password='testpass', salt='aD9ZLmkp', count=2048, prefix='$P$9aD9ZLmkp') + >>> phpass_passwd(password='testpass', salt='aD9ZLmkp', count=2048, prefix='$P$') '$P$9aD9ZLmkpsN4A83G8MefaaP888gVKX0' + >>> phpass_passwd(password='testpass', salt='Pb1j9gSb', count=2048, prefix='$H$') + '$H$9Pb1j9gSb/u3EVQ.4JDZ3LqtN44oIx/' + >>> phpass_passwd(password='testpass', salt='iwtD/g.K', count=128, prefix='$S$') + '$S$5iwtD/g.KZT2rwC9DASy/mGYAThkSd3lBFdkONi1Ig1IEpBpqG8W' """ def _encode64(input_, count): @@ -523,18 +530,24 @@ def wordpress_passwd(password, salt, count, prefix, **kwargs): return output password = getBytes(password) - salt = getBytes(salt) + f = {"$P$": md5, "$H$": md5, "$Q$": sha1, "$S$": sha512}[prefix] - cipher = md5(salt) + cipher = f(getBytes(salt)) cipher.update(password) hash_ = cipher.digest() for i in xrange(count): - _ = md5(hash_) + _ = f(hash_) _.update(password) hash_ = _.digest() - return "%s%s" % (prefix, _encode64(hash_, 16)) + retVal = "%s%s%s%s" % (prefix, ITOA64[int(math.log(count, 2))], salt, _encode64(hash_, len(hash_))) + + if prefix == "$S$": + # Reference: https://api.drupal.org/api/drupal/includes%21password.inc/constant/DRUPAL_HASH_LENGTH/7.x + retVal = retVal[:55] + + return retVal __functions__ = { HASH.MYSQL: mysql_passwd, @@ -555,7 +568,7 @@ __functions__ = { HASH.JOOMLA: joomla_passwd, HASH.DJANGO_MD5: django_md5_passwd, HASH.DJANGO_SHA1: django_sha1_passwd, - HASH.WORDPRESS: wordpress_passwd, + HASH.PHPASS: phpass_passwd, HASH.APACHE_MD5_CRYPT: unix_md5_passwd, HASH.UNIX_MD5_CRYPT: unix_md5_passwd, HASH.APACHE_SHA1: apache_sha1_passwd, @@ -965,7 +978,7 @@ def dictionaryAttack(attack_dict): try: item = None - if hash_regex not in (HASH.CRYPT_GENERIC, HASH.JOOMLA, HASH.WORDPRESS, HASH.UNIX_MD5_CRYPT, HASH.APACHE_MD5_CRYPT, HASH.APACHE_SHA1, HASH.VBULLETIN, HASH.VBULLETIN_OLD, HASH.SSHA, HASH.SSHA256, HASH.SSHA512, HASH.DJANGO_MD5, HASH.DJANGO_SHA1, HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): + if hash_regex not in (HASH.CRYPT_GENERIC, HASH.JOOMLA, HASH.PHPASS, HASH.UNIX_MD5_CRYPT, HASH.APACHE_MD5_CRYPT, HASH.APACHE_SHA1, HASH.VBULLETIN, HASH.VBULLETIN_OLD, HASH.SSHA, HASH.SSHA256, HASH.SSHA512, HASH.DJANGO_MD5, HASH.DJANGO_SHA1, HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): hash_ = hash_.lower() if hash_regex in (HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): @@ -994,9 +1007,9 @@ def dictionaryAttack(attack_dict): item = [(user, hash_), {"salt": hash_.split(':')[-1]}] elif hash_regex in (HASH.DJANGO_MD5, HASH.DJANGO_SHA1): item = [(user, hash_), {"salt": hash_.split('$')[1]}] - elif hash_regex in (HASH.WORDPRESS,): + elif hash_regex in (HASH.PHPASS,): if ITOA64.index(hash_[3]) < 32: - item = [(user, hash_), {"salt": hash_[4:12], "count": 1 << ITOA64.index(hash_[3]), "prefix": hash_[:12]}] + item = [(user, hash_), {"salt": hash_[4:12], "count": 1 << ITOA64.index(hash_[3]), "prefix": hash_[:3]}] else: warnMsg = "invalid hash '%s'" % hash_ logger.warn(warnMsg)