From 5e983434a194ce503959a166a5e2b8cd52bd61fb Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 29 Oct 2022 14:40:01 +0200 Subject: [PATCH] Use of a broken or weak cryptographic hashing algorithm (SHA256) on password storage #2175 --- glances/client_browser.py | 4 +-- glances/compat.py | 8 ++++++ glances/outputs/glances_bottle.py | 3 ++- glances/password.py | 42 ++++++++++++++++--------------- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/glances/client_browser.py b/glances/client_browser.py index 13161e3c..b52160fd 100644 --- a/glances/client_browser.py +++ b/glances/client_browser.py @@ -75,7 +75,7 @@ class GlancesClientBrowser(object): # Try with the preconfigure password (only if status is PROTECTED) clear_password = self.password.get_password(server['name']) if clear_password is not None: - server['password'] = self.password.sha256_hash(clear_password) + server['password'] = self.password.get_hash(clear_password) return 'http://{}:{}@{}:{}'.format(server['username'], server['password'], server['ip'], server['port']) else: return 'http://{}:{}'.format(server['ip'], server['port']) @@ -151,7 +151,7 @@ class GlancesClientBrowser(object): ) # Store the password for the selected server if clear_password is not None: - self.set_in_selected('password', self.password.sha256_hash(clear_password)) + self.set_in_selected('password', self.password.get_hash(clear_password)) # Display the Glance client on the selected server logger.info("Connect Glances client to the {} server".format(server['key'])) diff --git a/glances/compat.py b/glances/compat.py index 81fad5ad..48b35398 100644 --- a/glances/compat.py +++ b/glances/compat.py @@ -68,6 +68,10 @@ if PY3: return s.decode() return s.encode('ascii', 'ignore').decode() + def to_hex(s): + """Convert the bytes string to a hex string""" + return s.hex() + def listitems(d): return list(d.items()) @@ -166,6 +170,10 @@ else: return s return unicodedata.normalize('NFKD', s).encode('ascii', 'ignore') + def to_hex(s): + """Convert the string to a hex string in Python 2""" + return s.encode('hex') + def listitems(d): return d.items() diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index cdc54f0e..fc1edf38 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -129,7 +129,8 @@ class GlancesBottle(object): pwd = GlancesPassword(username=username, config=self.config) - return pwd.check_password(self.args.password, pwd.sha256_hash(password)) + return pwd.check_password(self.args.password, + pwd.get_hash(password)) else: return False diff --git a/glances/password.py b/glances/password.py index b34c13e0..7cf3fcc5 100644 --- a/glances/password.py +++ b/glances/password.py @@ -16,7 +16,7 @@ import sys import uuid from io import open -from glances.compat import b, input +from glances.compat import b, input, to_hex from glances.config import user_config_dir from glances.globals import safe_makedirs from glances.logger import logger @@ -36,7 +36,7 @@ class GlancesPassword(object): def local_password_path(self): """Return the local password path. - Related toissue: Password files in same configuration dir in effect #2143 + Related to issue: Password files in same configuration dir in effect #2143 """ if self.config is None: return user_config_dir() @@ -45,18 +45,19 @@ class GlancesPassword(object): 'local_password_path', default=user_config_dir()) - def sha256_hash(self, plain_password): - """Return the SHA-256 of the given password.""" - return hashlib.sha256(b(plain_password)).hexdigest() - - def get_hash(self, salt, plain_password): - """Return the hashed password, salt + SHA-256.""" - return hashlib.sha256(salt.encode() + plain_password.encode()).hexdigest() + def get_hash(self, plain_password, salt=''): + """Return the hashed password, salt + pbkdf2_hmac.""" + return to_hex(hashlib.pbkdf2_hmac('sha256', + plain_password.encode(), + salt.encode(), + 100000, + dklen=128)) def hash_password(self, plain_password): """Hash password with a salt based on UUID (universally unique identifier).""" salt = uuid.uuid4().hex - encrypted_password = self.get_hash(salt, plain_password) + encrypted_password = self.get_hash(plain_password, + salt=salt) return salt + '$' + encrypted_password def check_password(self, hashed_password, plain_password): @@ -65,7 +66,8 @@ class GlancesPassword(object): Return the comparison with the encrypted_password. """ salt, encrypted_password = hashed_password.split('$') - re_encrypted_password = self.get_hash(salt, plain_password) + re_encrypted_password = self.get_hash(plain_password, + salt = salt) return encrypted_password == re_encrypted_password def get_password(self, description='', confirm=False, clear=False): @@ -74,11 +76,11 @@ class GlancesPassword(object): For Glances server, get the password (confirm=True, clear=False): 1) from the password file (if it exists) 2) from the CLI - Optionally: save the password to a file (hashed with salt + SHA-256) + Optionally: save the password to a file (hashed with salt + SHA-pbkdf2_hmac) For Glances client, get the password (confirm=False, clear=True): 1) from the CLI - 2) the password is hashed with SHA-256 (only SHA string transit + 2) the password is hashed with SHA-pbkdf2_hmac (only SHA string transit through the network) """ if os.path.exists(self.password_file) and not clear: @@ -86,21 +88,21 @@ class GlancesPassword(object): logger.info("Read password from file {}".format(self.password_file)) password = self.load_password() else: - # password_sha256 is the plain SHA-256 password - # password_hashed is the salt + SHA-256 password - password_sha256 = self.sha256_hash(getpass.getpass(description)) - password_hashed = self.hash_password(password_sha256) + # password_hash is the plain SHA-pbkdf2_hmac password + # password_hashed is the salt + SHA-pbkdf2_hmac password + password_hash = self.get_hash(getpass.getpass(description)) + password_hashed = self.hash_password(password_hash) if confirm: # password_confirm is the clear password (only used to compare) - password_confirm = self.sha256_hash(getpass.getpass('Password (confirm): ')) + password_confirm = self.get_hash(getpass.getpass('Password (confirm): ')) if not self.check_password(password_hashed, password_confirm): logger.critical("Sorry, passwords do not match. Exit.") sys.exit(1) - # Return the plain SHA-256 or the salted password + # Return the plain SHA-pbkdf2_hmac or the salted password if clear: - password = password_sha256 + password = password_hash else: password = password_hashed