Verwendung eines Einweg-Hash mit vorhersagbarem Salt

Beschreibung

Die Verwendung eines Einweg-Hash mit vorhersagbarem Salt ist eine kryptografische Schwachstelle, bei der Software einen Einweg-Kryptografie-Hash für Daten verwendet, die irreversibel bleiben sollten (wie Passwörter), aber einen vorhersagbaren Salt während des Hashing-Prozesses implementiert. Ein Salt soll ein zufälliger Wert sein, der für jede Hash-Operation eindeutig ist und Vorberechnungsangriffe verhindert. Wenn der Salt vorhersagbar ist - wie die Verwendung des Benutzernamens, eines konstanten Werts oder eines von benutzersteuerbare Daten abgeleiteten Werts - können Angreifer Hashes für häufige Passwörter kombiniert mit bekannten Salts vorberechnen, was den Zweck des Saltings zunichtemacht.

Risiko

Vorhersagbare Salts schwächen die Passwortsicherheit erheblich. Angreifer können gezielte Rainbow Tables mit bekannten oder vorhersagbaren Salt-Werten erstellen. Wenn der Salt für alle Benutzer konstant ist, kompromittiert ein einziger Rainbow-Table-Angriff alle Passwörter. Wenn der Salt der Benutzername ist, können Angreifer Tabellen für häufige Benutzernamen vorberechnen. Selbst mit benutzerspezifischen Salts können Angreifer, wenn das Salt-Muster vorhersagbar ist (wie sequenzielle Nummern), gezielte Tabellen berechnen. Während Salts generische Rainbow-Table-Angriffe verhindern, bleiben vorhersagbare Salts anfällig für gezielte Wörterbuchangriffe, besonders mit moderner GPU-beschleunigter Hash-Crackning. Die Schwäche wird verstärkt, wenn sie mit schnellen Hash-Funktionen wie MD5 oder SHA-1 kombiniert wird.

Lösung

Verwenden Sie kryptografisch sichere Zufallszahlengeneratoren, um eindeutige Salts für jedes Passwort zu erstellen. Speichern Sie den Salt zusammen mit dem Hash. Verwenden Sie adaptive Passwort-Hashing-Funktionen wie bcrypt, scrypt, Argon2 oder PBKDF2, die Salting einbauen und rechenintensiv gestaltet sind. Diese Funktionen machen sowohl Rainbow-Table-Angriffe als auch Brute-Force-Angriffe unpraktisch. Konfigurieren Sie den Arbeitsfaktor hoch genug, um Angriffe kostspielig zu machen, während er für legitime Authentifizierung noch akzeptabel ist. Erhöhen Sie regelmäßig den Arbeitsfaktor mit steigender Rechenleistung.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - Vorhersagbare Salts ermöglichen Vorberechnungsangriffe, die Passwörter wiederherstellen können.
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Wiederhergestellte Passwörter können Zugang zu Benutzerkonten und sensiblen Daten ermöglichen.

Beispielcode + Lösungscode

Anfälliger Code

# Anfällig: Verwendung des Benutzernamens als Salt
import hashlib

def vulnerable_hash_password(username, password):
    # Anfällig: Benutzername ist vorhersagbar und Angreifern bekannt
    salt = username

    # Angreifer kann Hashes für häufige Benutzernamen vorberechnen
    hash_input = salt + password
    password_hash = hashlib.sha256(hash_input.encode()).hexdigest()

    return password_hash

# Anfällig: Verwendung eines konstanten Salts
CONSTANT_SALT = "MySuperSecretSalt123"  # Gleich für alle Benutzer!

def vulnerable_constant_salt(password):
    # Anfällig: Eine Rainbow Table greift ALLE Passwörter an
    hash_input = CONSTANT_SALT + password
    return hashlib.sha256(hash_input.encode()).hexdigest()

# Anfällig: Verwendung eines vorhersagbar abgeleiteten Salts
def vulnerable_derived_salt(user_id, password):
    # Anfällig: Salt von sequenzieller Benutzer-ID abgeleitet
    salt = f"salt_{user_id}"  # user_id ist 1, 2, 3...

    # Angreifer können alle möglichen Salts aufzählen
    return hashlib.sha256((salt + password).encode()).hexdigest()
<?php
// Anfällig: Fest kodierter Salt (CVE-2008-4905-Muster)
define('PASSWORD_SALT', 'blog_secret_2008');

function vulnerable_hash($password) {
    // Anfällig: Gleicher Salt für alle Passwörter
    return md5(PASSWORD_SALT . $password);
}

// Anfällig: Verwendung der E-Mail-Domain als Salt
function vulnerable_email_salt($email, $password) {
    // Anfällig: Domain ist vorhersagbar
    $domain = explode('@', $email)[1];  // z.B. "gmail.com"

    // Angreifer kann Tabellen für häufige Domains erstellen
    return hash('sha256', $domain . $password);
}

// Anfällig: Sequenzieller Salt (CVE-2002-1657-Muster)
function vulnerable_sequential_salt($user_id, $password) {
    // Anfällig: Vorhersagbare Sequenz
    $salt = $user_id;  // 1, 2, 3, 4...

    return hash('sha256', $salt . $password);
}
?>
// Anfällig: Java mit vorhersagbarem Salt
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;

public class VulnerableHashing {

    // Anfällig: Statischer konstanter Salt
    private static final String SALT = "StaticSalt2024";

    public String vulnerableHash(String password) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            // Anfällig: Gleicher Salt für alle
            String salted = SALT + password;
            byte[] hash = md.digest(salted.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(hash);
        } catch (Exception e) {
            return null;
        }
    }

    // Anfällig: Salt basierend auf Benutzerattribut
    public String vulnerableUserSalt(User user, String password) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            // Anfällig: Benutzername ist vorhersagbar
            String salted = user.getUsername() + password;
            byte[] hash = md.digest(salted.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(hash);
        } catch (Exception e) {
            return null;
        }
    }
}
// Anfällig: C mit konstantem Salt (CVE-2001-0967-Muster)
#include <string.h>
#include <openssl/sha.h>

#define CONSTANT_SALT "ServerSalt"

void vulnerable_hash(const char* password, unsigned char* output) {
    char buffer[256];

    // Anfällig: Konstanter Salt
    snprintf(buffer, sizeof(buffer), "%s%s", CONSTANT_SALT, password);

    SHA256((unsigned char*)buffer, strlen(buffer), output);
}

// Anfällig: Salt von Zeitstempel mit niedriger Auflösung
void vulnerable_time_salt(const char* password, unsigned char* output) {
    char buffer[256];
    time_t now = time(NULL);

    // Anfällig: Nur ~31M mögliche Werte pro Jahr
    // Angreifer kann Zeitstempel um Kontoerstellung herum aufzählen
    snprintf(buffer, sizeof(buffer), "%ld%s", now, password);

    SHA256((unsigned char*)buffer, strlen(buffer), output);
}

Behobener Code

# Behoben: Ordentliches Passwort-Hashing mit zufälligem Salt verwenden
import bcrypt
import argon2
import secrets

# Behoben: Verwendung von bcrypt (empfohlen)
def secure_hash_bcrypt(password):
    # bcrypt generiert zufälligen Salt intern
    # Arbeitsfaktor einstellbar für Sicherheit/Leistung-Balance
    salt = bcrypt.gensalt(rounds=12)
    password_hash = bcrypt.hashpw(password.encode(), salt)
    return password_hash

def verify_bcrypt(password, stored_hash):
    return bcrypt.checkpw(password.encode(), stored_hash)

# Behoben: Verwendung von Argon2 (aktuelle Empfehlung)
def secure_hash_argon2(password):
    ph = argon2.PasswordHasher(
        time_cost=3,       # Anzahl der Iterationen
        memory_cost=65536, # 64MB Speicherverbrauch
        parallelism=4      # Parallele Threads
    )
    return ph.hash(password)

def verify_argon2(password, stored_hash):
    ph = argon2.PasswordHasher()
    try:
        return ph.verify(stored_hash, password)
    except argon2.exceptions.VerifyMismatchError:
        return False

# Behoben: Manueller Ansatz mit kryptografisch zufälligem Salt
import hashlib

def secure_hash_manual(password):
    # Behoben: Kryptografisch zufälligen Salt generieren
    salt = secrets.token_bytes(32)  # 256 Bits Zufälligkeit

    # Langsame Hash-Funktion verwenden
    # Hinweis: Besser ist immer noch bcrypt/argon2
    hash_value = hashlib.pbkdf2_hmac(
        'sha256',
        password.encode(),
        salt,
        iterations=100000  # Hohe Iterationszahl
    )

    # Salt + Hash zurückgeben (Salt für Verifizierung benötigt)
    return salt + hash_value
<?php
// Behoben: PHP Passwort-Hashing (PHP 5.5+)
function secure_hash($password) {
    // Behoben: password_hash generiert zufälligen Salt automatisch
    // Verwendet standardmäßig bcrypt (PASSWORD_DEFAULT)
    return password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);
}

function secure_verify($password, $stored_hash) {
    return password_verify($password, $stored_hash);
}

// Behoben: Verwendung von Argon2 (PHP 7.2+)
function secure_hash_argon2($password) {
    return password_hash($password, PASSWORD_ARGON2ID, [
        'memory_cost' => 65536,
        'time_cost' => 4,
        'threads' => 3
    ]);
}

// Behoben: Manueller Ansatz mit ordentlichem Salt
function secure_manual_hash($password) {
    // Behoben: Kryptografisch zufälliger Salt
    $salt = random_bytes(32);

    // PBKDF2 mit hohen Iterationen verwenden
    $hash = hash_pbkdf2('sha256', $password, $salt, 100000, 32, true);

    // Kodierten Salt und Hash zurückgeben
    return base64_encode($salt) . '$' . base64_encode($hash);
}
?>
// Behoben: Java mit ordentlichem Passwort-Hashing
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;

public class SecureHashing {

    // Behoben: Verwendung von BCrypt
    private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);

    public String secureBcryptHash(String password) {
        // BCrypt generiert zufälligen Salt intern
        return encoder.encode(password);
    }

    public boolean secureBcryptVerify(String password, String hash) {
        return encoder.matches(password, hash);
    }

    // Behoben: Verwendung von PBKDF2 mit zufälligem Salt
    public String securePbkdf2Hash(String password) throws Exception {
        // Behoben: Zufälligen Salt generieren
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[32];
        random.nextBytes(salt);

        // Hohe Iterationszahl
        int iterations = 100000;

        PBEKeySpec spec = new PBEKeySpec(
            password.toCharArray(), salt, iterations, 256);
        SecretKeyFactory factory =
            SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        byte[] hash = factory.generateSecret(spec).getEncoded();

        // Iterationen:Salt:Hash zurückgeben
        return iterations + ":" + bytesToHex(salt) + ":" + bytesToHex(hash);
    }
}
// Behoben: C mit ordentlicher Salt-Generierung
#include <string.h>
#include <openssl/rand.h>
#include <openssl/evp.h>

#define SALT_LENGTH 32
#define HASH_LENGTH 32
#define ITERATIONS 100000

int secure_hash(const char* password,
                unsigned char* salt_out,
                unsigned char* hash_out) {

    // Behoben: Kryptografisch zufälligen Salt generieren
    if (RAND_bytes(salt_out, SALT_LENGTH) != 1) {
        return -1;  // RNG-Fehler
    }

    // PBKDF2 mit hohen Iterationen verwenden
    if (PKCS5_PBKDF2_HMAC(password, strlen(password),
                          salt_out, SALT_LENGTH,
                          ITERATIONS,
                          EVP_sha256(),
                          HASH_LENGTH, hash_out) != 1) {
        return -1;
    }

    return 0;
}

int secure_verify(const char* password,
                  const unsigned char* stored_salt,
                  const unsigned char* stored_hash) {

    unsigned char computed_hash[HASH_LENGTH];

    // Hash mit gespeichertem Salt neu berechnen
    if (PKCS5_PBKDF2_HMAC(password, strlen(password),
                          stored_salt, SALT_LENGTH,
                          ITERATIONS,
                          EVP_sha256(),
                          HASH_LENGTH, computed_hash) != 1) {
        return 0;
    }

    // Konstantzeit-Vergleich
    return CRYPTO_memcmp(computed_hash, stored_hash, HASH_LENGTH) == 0;
}

CVE-Beispiele

  • CVE-2008-4905: Blogging-Software verwendete fest kodierten Salt für Passwort-Hashing.
  • CVE-2002-1657: Datenbankserver verwendete Benutzernamen als Salt, was gezielte Brute-Force-Angriffe ermöglichte.
  • CVE-2001-0967: Server verwendete konstanten Salt für alle Passwörter.
  • CVE-2005-0408: Vorhersagbare MD5-Hashes mit konstanten Werten plus Benutzernamen ermöglichten Authentifizierungsumgehung.

Referenzen

  1. MITRE Corporation. "CWE-760: Use of a One-Way Hash with a Predictable Salt." https://cwe.mitre.org/data/definitions/760.html
  2. OWASP. "Password Storage Cheat Sheet."
  3. NIST SP 800-132. "Recommendation for Password-Based Key Derivation."