Weakness reference
CWE-836

Use of Password Hash Instead of Password for Authentication

This weakness occurs when a system treats a password hash as if it were a password itself, rather than as a one-way cryptographic representation. If an…

01Summary

This weakness occurs when a system treats a password hash as if it were a password itself, rather than as a one-way cryptographic representation. If an attacker obtains a stored hash, they can use it directly to authenticate without needing to crack or reverse it. This turns password hashing—a critical security control—into a false sense of protection.

02How It Happens

Password hashing is meant to be a one-way function: the system hashes a user-submitted password and compares it to a stored hash. The hash itself should never be usable as a credential. However, some systems make a critical mistake: they accept a hash value submitted during login and compare it directly to the stored hash, rather than hashing the submitted password first. This happens when developers conflate "the hash is stored securely" with "the hash cannot be used as a password." In reality, if an attacker steals the hash from a database or log file, they can replay it directly to the authentication system without ever knowing the original password.

03Real-World Impact

A stolen password hash becomes a valid credential. An attacker who gains access to a database dump, backup file, or log containing password hashes can use those hashes to log in as any user without cracking them. This is particularly dangerous because hashes are often considered "safe to expose" compared to plaintext passwords, leading to weaker protection of systems that store them. Account takeover, privilege escalation, and lateral movement across systems become trivial.

04Vulnerable & Fixed Patterns

Vulnerable pattern
import hashlib

def authenticate_user(username, submitted_credential):
    stored_hash = get_stored_hash(username)
    # BUG: comparing hash directly to hash
    if submitted_credential == stored_hash:
        return True
    return False

# Attacker who stole the hash can log in:
stolen_hash = "a1b2c3d4e5f6..."
authenticate_user("alice", stolen_hash)  # Returns True

Why it's vulnerable:
The function accepts a hash as a credential and compares it directly to the stored hash. An attacker with a stolen hash can authenticate without knowing the password.

Fixed pattern
import hashlib

def authenticate_user(username, submitted_password):
    stored_hash = get_stored_hash(username)
    # Hash the submitted password, then compare
    submitted_hash = hashlib.sha256(submitted_password.encode()).hexdigest()
    if submitted_hash == stored_hash:
        return True
    return False

# Attacker with a stolen hash cannot log in:
stolen_hash = "a1b2c3d4e5f6..."
authenticate_user("alice", stolen_hash)  # Returns False (hash is hashed again)
Vulnerable pattern
<?php
function authenticate_user($username, $submitted_credential) {
    $stored_hash = get_stored_hash($username);
    // BUG: comparing hash directly to hash
    if ($submitted_credential === $stored_hash) {
        return true;
    }
    return false;
}

// Attacker who stole the hash can log in:
$stolen_hash = "a1b2c3d4e5f6...";
authenticate_user("alice", $stolen_hash);  // Returns true
?>

Why it's vulnerable:
The function treats the submitted credential as a hash and compares it directly to storage, allowing a stolen hash to be used as a password.

Fixed pattern
<?php
function authenticate_user($username, $submitted_password) {
    $stored_hash = get_stored_hash($username);
    // Hash the submitted password, then compare
    $submitted_hash = hash('sha256', $submitted_password);
    if ($submitted_hash === $stored_hash) {
        return true;
    }
    return false;
}

// Attacker with a stolen hash cannot log in:
$stolen_hash = "a1b2c3d4e5f6...";
authenticate_user("alice", $stolen_hash);  // Returns false (hash is hashed again)
?>

05Prevention Checklist

Always hash the submitted password before comparison.
Never accept a hash as a login credential; only accept plaintext passwords and hash them in-memory.
Use a modern hashing algorithm.
Prefer bcrypt, scrypt, or argon2 over SHA-256 or MD5. These are designed to be slow and resistant to brute-force attacks.
Never log or transmit password hashes.
Treat hashes as sensitive as passwords; if they leak, they become valid credentials.
Implement rate limiting and account lockout.
Even with correct hashing, limit login attempts to slow brute-force attacks.
Use salts and unique per-user values.
Modern algorithms like bcrypt handle this automatically; ensure your implementation does too.
Audit authentication code for hash-to-hash comparisons.
Search your codebase for patterns where a submitted value is compared directly to a stored hash without re-hashing.

06Signs You May Already Be Affected

Review your authentication logs and code for patterns where hashes appear in login requests or where authentication logic compares two hash values directly without re-hashing the submitted credential. If your system accepts hexadecimal or base64-encoded strings as passwords, or if your password reset flow transmits hashes instead of temporary tokens, this weakness may be present.

07Related Recent Vulnerabilities