Verwendung eines schwachen Hashes
Beschreibung
Verwendung eines schwachen Hashes ist eine Schwachstelle, die auftritt, wenn ein Produkt einen Hashing-Algorithmus verwendet, der die Sicherheitsanforderungen nicht erfüllt, wodurch Angreifer ursprüngliche Eingabewerte ermitteln, Hash-Kollisionen finden oder Birthday-Angriffe effizienter durchführen können als Brute-Force-Methoden es erlauben würden. Eine kryptografische Hash-Funktion muss rechnerisch unmöglich zu invertieren sein (Einwegfunktion), deterministisch dieselbe Ausgabe für identische Eingaben produzieren und gegen Preimage-, Second-Preimage- und Birthday-Angriffe resistent sein. Schwache Hash-Funktionen einschließlich MD5, SHA-1 und ungesalzener SHA-2-Varianten erfüllen diese Anforderungen für sicherheitskritische Anwendungen nicht, insbesondere für Passwortspeicherung, wo Rainbow-Table-Angriffe hocheffektiv sind.
Risiko
Schwache Hash-Funktionen setzen Anwendungen mehreren Angriffsvektoren aus. Rainbow Tables, die vorberechnete Hashes für gängige Passwörter enthalten, können MD5- oder SHA-1-Passwort-Hashes ohne Salting sofort umkehren. Kollisionsangriffe auf MD5 und SHA-1 ermöglichen Zertifikatsfälschung, wodurch Angreifer bösartige Zertifikate mit demselben Hash wie legitime erstellen können. Schnelle Hash-Funktionen wie MD5, SHA-1 und sogar SHA-256 ermöglichen schnelle Brute-Force-Angriffe mit GPUs oder spezialisierter Hardware. Für Passwortspeicherung können moderne GPUs Milliarden von MD5-Hashes pro Sekunde berechnen. Das Risiko erstreckt sich über das Passwort-Knacken hinaus: Integritätsverifizierung mit schwachen Hashes kann durch Erstellen kollidierender Eingaben umgangen werden, digitale Signaturen werden fälschbar, und inhaltsadressierte Speichersysteme werden anfällig für Manipulation. Diese Angriffe sind nicht theoretisch - sie wurden praktisch demonstriert und in der Praxis ausgenutzt.
Lösung
Verwenden Sie kryptografische Hash-Funktionen, die für den spezifischen Anwendungsfall geeignet sind. Für Passwortspeicherung verwenden Sie adaptive Hash-Funktionen mit konfigurierbaren Arbeitsfaktoren: bcrypt, scrypt oder Argon2id. Diese Funktionen integrieren Salts automatisch und ermöglichen die Anpassung der Rechenkosten, um sicher zu bleiben, wenn Hardware besser wird. Für allgemeine Integritätsverifizierung und digitale Signaturen verwenden Sie SHA-256 oder SHA-3. Verwenden Sie niemals MD5, SHA-1 oder ungesalzene Hashes für Sicherheitszwecke. Bei der Aufrüstung von schwachen Hashes implementieren Sie eine schrittweise Migration, die Passwörter bei erfolgreicher Anmeldung neu hasht. Für HMAC-Konstruktion sind sogar MD5 und SHA-1 aufgrund der Sicherheitseigenschaften von HMAC akzeptabel, aber SHA-256 wird bevorzugt. Konfigurieren Sie geeignete Arbeitsfaktoren unter Berücksichtigung von sowohl Sicherheit als auch Denial-of-Service-Risiken durch teure Hash-Operationen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Umfang: Zugriffskontrolle Angreifer können die Authentifizierung umgehen, indem sie Passwörter aus schwachen Hashes mit Rainbow Tables, Brute-Force-Angriffen oder vorberechneten Wörterbüchern wiederherstellen. |
| Integrität | Umfang: Integrität Kollisionsangriffe auf schwache Hashes ermöglichen Angreifern, Daten mit übereinstimmenden Hashes zu fälschen, wodurch die Integritätsverifizierung umgangen wird und möglicherweise Code- oder Zertifikatssubstitution ermöglicht wird. |
| Nicht-Abstreitbarkeit | Umfang: Nicht-Abstreitbarkeit Digitale Signaturen, die schwache Hashes verwenden, können durch Kollisionsangriffe gefälscht werden, was die Fähigkeit untergräbt, Urheberschaft zu beweisen oder Manipulation zu erkennen. |
Beispielcode
Anfälliger Code (Python/Java)
Die folgenden Beispiele demonstrieren die Verwendung schwacher Hash-Funktionen:
# Anfällig: Verwendung schwacher Hash-Funktionen
import hashlib
# Anfällig: MD5 für Passwortspeicherung
def vulnerable_store_password(password):
# Anfällig: MD5 ist kryptografisch gebrochen
# Rainbow Tables können diese sofort knacken
return hashlib.md5(password.encode()).hexdigest()
def vulnerable_verify_password(password, stored_hash):
return hashlib.md5(password.encode()).hexdigest() == stored_hash
# Anfällig: SHA-1 für Integrität
def vulnerable_compute_checksum(data):
# Anfällig: SHA-1 hat praktische Kollisionsangriffe
return hashlib.sha1(data).hexdigest()
# Anfällig: Ungesalzenes SHA-256 für Passwörter
def vulnerable_sha256_password(password):
# Anfällig: Ohne Salt produzieren identische Passwörter identische Hashes
# Rainbow Tables existieren auch für SHA-256-Passwörter
return hashlib.sha256(password.encode()).hexdigest()
# Anfällig: CRC32 für Integrität (überhaupt nicht kryptografisch)
import zlib
def vulnerable_crc_checksum(data):
# Anfällig: CRC32 ist KEINE kryptografische Hash-Funktion
# Trivial, Kollisionen zu finden
return hex(zlib.crc32(data) & 0xffffffff)
# Anfällig: Einfacher gesalzener Hash mit schnellem Algorithmus
def vulnerable_salted_md5(password, salt):
# Anfällig: MD5 ist zu schnell, selbst mit Salt
# GPUs können Milliarden pro Sekunde versuchen
return hashlib.md5((salt + password).encode()).hexdigest()
# Anfällig: Einzelne Iteration eines beliebigen Hashes
def vulnerable_single_hash(password):
# Anfällig: Einzelne Iteration ist zu schnell
return hashlib.sha512(password.encode()).hexdigest()
// Anfällig: Verwendung schwacher Hash-Funktionen in Java
import java.security.*;
import java.util.*;
public class VulnerableHashing {
// Anfällig: MD5 für Passwortspeicherung
public String vulnerableMd5Password(String password) throws Exception {
// Anfällig: MD5 ist gebrochen
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(password.getBytes("UTF-8"));
return bytesToHex(hash);
}
// Anfällig: SHA-1 für Integrität
public String vulnerableSha1Checksum(byte[] data) throws Exception {
// Anfällig: SHA-1 hat Kollisionsangriffe
MessageDigest md = MessageDigest.getInstance("SHA-1");
return bytesToHex(md.digest(data));
}
// Anfällig: Ungesalzenes SHA-256
public String vulnerableSha256(String password) throws Exception {
// Anfällig: Kein Salt bedeutet Rainbow Tables funktionieren
MessageDigest md = MessageDigest.getInstance("SHA-256");
return bytesToHex(md.digest(password.getBytes()));
}
// Anfällig: Benutzerdefinierte Hash-Kombination
public String vulnerableCustomHash(String password) throws Exception {
// Anfällig: Kombination schwacher Hashes macht sie nicht starker
MessageDigest md5 = MessageDigest.getInstance("MD5");
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
byte[] md5Hash = md5.digest(password.getBytes());
byte[] sha1Hash = sha1.digest(password.getBytes());
// XOR-Verknüpfung schwacher Hashes
byte[] combined = new byte[16];
for (int i = 0; i < 16; i++) {
combined[i] = (byte)(md5Hash[i] ^ sha1Hash[i]);
}
return bytesToHex(combined);
}
// Anfällig: Hash für Verschlüsselung verwenden
public byte[] vulnerableHashEncrypt(byte[] data, String key) throws Exception {
// Anfällig: Hash-Funktionen sind keine Verschlüsselung!
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] keyHash = md.digest(key.getBytes());
// XOR mit Hash - das ist KEINE sichere Verschlüsselung
byte[] result = new byte[data.length];
for (int i = 0; i < data.length; i++) {
result[i] = (byte)(data[i] ^ keyHash[i % keyHash.length]);
}
return result;
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
// Anfällig: Verwendung schwacher Hash-Funktionen in C
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <string.h>
#include <stdio.h>
// Anfällig: MD5-Passwort-Hash
void vulnerable_md5_password(const char *password, char *output) {
unsigned char hash[MD5_DIGEST_LENGTH];
// Anfällig: MD5 ist gebrochen
MD5((unsigned char*)password, strlen(password), hash);
for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
sprintf(output + (i * 2), "%02x", hash[i]);
}
}
// Anfällig: SHA-1-Integritätsprüfung
void vulnerable_sha1_file(const char *filename, unsigned char *output) {
// Anfällig: SHA-1-Kollisionsangriffe sind praktisch
FILE *f = fopen(filename, "rb");
SHA_CTX ctx;
SHA1_Init(&ctx);
unsigned char buffer[4096];
size_t bytes;
while ((bytes = fread(buffer, 1, sizeof(buffer), f)) > 0) {
SHA1_Update(&ctx, buffer, bytes);
}
SHA1_Final(output, &ctx);
fclose(f);
}
// Anfällig: Einfacher gesalzener Hash
void vulnerable_salted_hash(const char *password, const char *salt,
char *output) {
char combined[256];
snprintf(combined, sizeof(combined), "%s%s", salt, password);
// Anfällig: Einzelnes SHA-256 ist zu schnell
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)combined, strlen(combined), hash);
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(output + (i * 2), "%02x", hash[i]);
}
}
Korrigierter Code (Python/Java)
# Korrigiert: Verwendung sicherer Hash-Funktionen
import bcrypt
import hashlib
import hmac
import secrets
# Korrigiert: bcrypt für Passwortspeicherung
def secure_store_password(password):
# Korrigiert: bcrypt mit automatischem Salt und Arbeitsfaktor
return bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
def secure_verify_password(password, stored_hash):
# Korrigiert: Zeitkonstanter Vergleich in bcrypt integriert
return bcrypt.checkpw(password.encode(), stored_hash)
# Korrigiert: Argon2 für Passwortspeicherung (bevorzugt)
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
def secure_argon2_password(password):
# Korrigiert: Argon2id mit speicherintensiven Eigenschaften
ph = PasswordHasher(
time_cost=3,
memory_cost=65536, # 64 MB
parallelism=4
)
return ph.hash(password)
def secure_verify_argon2(password, hash):
ph = PasswordHasher()
try:
ph.verify(hash, password)
return True
except VerifyMismatchError:
return False
# Korrigiert: SHA-256 für Integrität (wo Kollisionsresistenz weniger wichtig ist)
def secure_compute_checksum(data):
# Korrigiert: SHA-256 für allgemeine Integrität
return hashlib.sha256(data).hexdigest()
# Korrigiert: SHA-3 für höchste Sicherheit
def secure_sha3_hash(data):
# Korrigiert: SHA-3 hat andere Konstruktion als SHA-2
return hashlib.sha3_256(data).hexdigest()
# Korrigiert: HMAC für verschlüsseltes Hashing
def secure_hmac(key, data):
# Korrigiert: HMAC bietet Authentifizierung
return hmac.new(key, data, hashlib.sha256).hexdigest()
# Korrigiert: Zeitkonstanter Vergleich
def secure_compare_hashes(hash1, hash2):
# Korrigiert: Timing-Angriffe verhindern
return hmac.compare_digest(hash1, hash2)
# Korrigiert: PBKDF2 als Alternative zu bcrypt
def secure_pbkdf2_password(password, salt=None):
if salt is None:
salt = secrets.token_bytes(16)
# Korrigiert: Hohe Iterationsanzahl
dk = hashlib.pbkdf2_hmac(
'sha256',
password.encode(),
salt,
600000 # OWASP empfohlenes Minimum
)
return salt + dk
// Korrigiert: Verwendung sicherer Hash-Funktionen in Java
import org.mindrot.jbcrypt.BCrypt;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.util.*;
public class SecureHashing {
// Korrigiert: bcrypt für Passwortspeicherung
public String secureHashPassword(String password) {
// Korrigiert: bcrypt mit automatischem Salt und Arbeitsfaktor
return BCrypt.hashpw(password, BCrypt.gensalt(12));
}
public boolean secureVerifyPassword(String password, String hash) {
// Korrigiert: Zeitkonstanter Vergleich
return BCrypt.checkpw(password, hash);
}
// Korrigiert: PBKDF2 für passwortbasierte Schlüsselableitung
public byte[] securePbkdf2(String password, byte[] salt) throws Exception {
// Korrigiert: Hohe Iterationsanzahl
SecretKeyFactory factory = SecretKeyFactory.getInstance(
"PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(
password.toCharArray(),
salt,
600000, // Hohe Iterationen
256
);
return factory.generateSecret(spec).getEncoded();
}
// Korrigiert: SHA-256 für Integrität
public byte[] secureChecksum(byte[] data) throws Exception {
// Korrigiert: SHA-256 für allgemeine Integrität
MessageDigest md = MessageDigest.getInstance("SHA-256");
return md.digest(data);
}
// Korrigiert: SHA-3 für höchste Sicherheit
public byte[] secureSha3(byte[] data) throws Exception {
// Korrigiert: SHA3-256
MessageDigest md = MessageDigest.getInstance("SHA3-256");
return md.digest(data);
}
// Korrigiert: HMAC für verschlüsseltes Hashing
public byte[] secureHmac(byte[] key, byte[] data) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(key, "HmacSHA256");
mac.init(keySpec);
return mac.doFinal(data);
}
// Korrigiert: Zeitkonstanter Hash-Vergleich
public boolean secureCompare(byte[] hash1, byte[] hash2) {
// Korrigiert: MessageDigest.isEqual ist zeitkonstant
return MessageDigest.isEqual(hash1, hash2);
}
// Korrigiert: Sicheres Salt generieren
public byte[] generateSalt() {
byte[] salt = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(salt);
return salt;
}
}
// Korrigiert: Verwendung sicherer Hash-Funktionen in C
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <argon2.h>
#include <string.h>
// Korrigiert: Argon2id für Passwort-Hashing
int secure_hash_password(const char *password, char *output, size_t output_len) {
uint8_t salt[16];
// Zufälliges Salt generieren
if (RAND_bytes(salt, sizeof(salt)) != 1) {
return -1;
}
// Korrigiert: Argon2id mit sicheren Parametern
int result = argon2id_hash_encoded(
3, // time_cost
65536, // memory_cost (64 MB)
4, // parallelism
password, strlen(password),
salt, sizeof(salt),
32, // Hash-Länge
output, output_len
);
return result == ARGON2_OK ? 0 : -1;
}
int secure_verify_password(const char *password, const char *hash) {
// Korrigiert: Argon2-Verifizierung mit zeitkonstantem Vergleich
return argon2id_verify(hash, password, strlen(password)) == ARGON2_OK;
}
// Korrigiert: SHA-256 für Integrität
int secure_sha256(const unsigned char *data, size_t len,
unsigned char *output) {
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
// Korrigiert: SHA-256 mit EVP-Schnittstelle
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
EVP_DigestUpdate(ctx, data, len);
unsigned int hash_len;
EVP_DigestFinal_ex(ctx, output, &hash_len);
EVP_MD_CTX_free(ctx);
return hash_len;
}
// Korrigiert: SHA-3 für höchste Sicherheit
int secure_sha3_256(const unsigned char *data, size_t len,
unsigned char *output) {
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
// Korrigiert: SHA3-256
EVP_DigestInit_ex(ctx, EVP_sha3_256(), NULL);
EVP_DigestUpdate(ctx, data, len);
unsigned int hash_len;
EVP_DigestFinal_ex(ctx, output, &hash_len);
EVP_MD_CTX_free(ctx);
return hash_len;
}
// Korrigiert: Zeitkonstanter Vergleich
int secure_compare(const unsigned char *a, const unsigned char *b, size_t len) {
// Korrigiert: CRYPTO_memcmp ist zeitkonstant
return CRYPTO_memcmp(a, b, len) == 0;
}
// Korrigiert: PBKDF2 für Schlüsselableitung
int secure_pbkdf2(const char *password, const unsigned char *salt,
size_t salt_len, unsigned char *key, size_t key_len) {
// Korrigiert: Hohe Iterationsanzahl
return PKCS5_PBKDF2_HMAC(
password, strlen(password),
salt, salt_len,
600000, // Hohe Iterationen
EVP_sha256(),
key_len, key
);
}
Die Korrektur verwendet bcrypt, Argon2 oder PBKDF2 für Passwörter und SHA-256/SHA-3 für Integritätsverifizierung.
Ausgenutzt in der Praxis
LinkedIn-Passwort-Datenleck (2012)
6,5 Millionen LinkedIn-Passwort-Hashes wurden geleakt, gespeichert als ungesalzenes SHA-1. Die meisten wurden innerhalb von Tagen mit Rainbow Tables und GPU-basierten Angriffen geknackt.
Flame-Malware MD5-Kollision (2012)
Die Flame-Malware verwendete einen MD5-Kollisionsangriff, um Windows-Update-Zertifikate zu fälschen, was die Malware-Verteilung ermöglichte, die von Microsoft zu stammen schien.
Tools zum Testen/Ausnutzen
-
hashcat — GPU-beschleunigter Passwort-Hash-Cracker, der viele schwache Algorithmen unterstützt.
-
John the Ripper — Klassischer Passwort-Cracker mit umfangreicher Format-Unterstützung.
-
Rainbow Crack — Rainbow-Table-Generierung und -Suche.
CVE-Beispiele
-
CVE-2005-4900 — SHA-1 fehlt Kollisionsresistenz.
-
CVE-2012-6707 — Produkt verwendet MD5-basierten Passwortalgorithmus.
-
CVE-2019-14855 — Zertifikatssignatur-Fälschung über SHA-1-Kollisionen.
Referenzen
-
MITRE Corporation. "CWE-328: Use of Weak Hash." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/328.html
-
OWASP Foundation. "Password Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
-
NIST. "Digital Identity Guidelines." SP 800-63B. https://pages.nist.gov/800-63-3/sp800-63b.html