Fehlender kryptografischer Schritt

Beschreibung

Fehlender kryptografischer Schritt ist eine Schwachstelle, die auftritt, wenn ein Produkt einen erforderlichen Schritt in einem kryptografischen Algorithmus nicht implementiert, was zu schwächerer Verschlüsselung oder Sicherheit führt, als der Algorithmus bieten soll. Dies kann passieren, wenn Entwickler rechenintensive Schritte auslassen, um die Leistung zu verbessern, Algorithmusanforderungen missverstehen oder mit unvollständigen Spezifikationen arbeiten. Die resultierende Implementierung scheint zu funktionieren, bietet aber reduzierte Sicherheitsgarantien. Beispiele umfassen HMAC-Implementierungen, die keine Nachrichten verarbeiten können, die länger als ein einzelner Block sind, Verschlüsselung ohne ordnungsgemäße Initialisierungsvektor-Handhabung oder Signaturverifizierung, die kritische Validierungsschritte überspringt.

Risiko

Fehlende kryptografische Schritte können die Sicherheit katastrophal schwächen, während sie korrekt zu funktionieren scheinen. Die unvollständige Implementierung kann grundlegende Funktionstests bestehen, während sie für kryptografische Angriffe anfällig ist. Ausgelassene Schritte umfassen oft solche, die kritische Sicherheitseigenschaften bieten - das Überspringen eines Authentifizierungsschritts entfernt den Integritätsschutz, das Auslassen von Schlüsselableitungsschritten kann zu vorhersagbaren Schlüsseln führen, und fehlende Verifizierungsschritte können ermöglichen, dass gefälschte Daten akzeptiert werden. Das Risiko wird verstärkt, weil diese Schwachstellen durch normale Tests möglicherweise nicht erkennbar sind und kryptografische Expertise erfordern, um identifiziert zu werden. Angreifer mit Kenntnis der spezifischen Auslassung können sie ausnutzen und möglicherweise die Verschlüsselung vollständig brechen oder die Authentifizierung umgehen. Die Auswirkung hängt davon ab, welcher Schritt ausgelassen wurde, kann aber von verminderter Sicherheit bis zum vollständigen kryptografischen Versagen reichen.

Lösung

Implementieren Sie kryptografische Algorithmen genau wie in ihren Standards spezifiziert, ohne Optimierungsabkürzungen, die Schritte überspringen. Verwenden Sie gut getestete kryptografische Bibliotheken anstatt Algorithmen von Grund auf neu zu implementieren. Wenn Sie kryptografische Primitive implementieren, folgen Sie den Spezifikationen genau und verifizieren Sie Implementierungen gegen Testvektoren. Führen Sie kryptografische Code-Reviews durch Sicherheitsexperten durch, die die Algorithmusanforderungen verstehen. Testen Sie Implementierungen mit Randfällen einschließlich sehr länger Nachrichten, leerer Eingaben und Grenzbedingungen. Vermeiden Sie die Versuchung, "teure" Schritte für Leistung zu überspringen - wenn Leistung kritisch ist, wählen Sie schnellere Algorithmen anstatt Implementierungen zu schwächen. Dokumentieren Sie alle Abweichungen von Standardimplementierungen und analysieren Sie deren Sicherheitsauswirkungen. Verwenden Sie formale Verifizierungsmethoden für kritischen kryptografischen Code, wo möglich.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Zugriffskontrolle

Fehlende Authentifizierungs- oder Verifizierungsschritte können Angreifern ermöglichen, Schutzmechanismen zu umgehen und gültig aussehende Daten oder Anmeldedaten zu fälschen.
Vertraulichkeit, IntegritätUmfang: Vertraulichkeit, Integrität

Unvollständige Verschlüsselungsimplementierungen können anfällig für kryptanalytische Angriffe sein, die Klartext wiederherstellen. Fehlende Integritätsschritte ermöglichen unerkannte Datenmodifikation.
Nicht-AbstreitbarkeitUmfang: Nicht-Abstreitbarkeit

Fehlende Schritte in Implementierungen digitaler Signaturen können Signaturfälschung ermöglichen, was die Rechenschaftspflicht untergräbt und Angreifern ermöglicht, ihre Aktivitäten zu verbergen.

Beispielcode

Anfälliger Code (Verilog/C)

Die folgenden Beispiele demonstrieren fehlende kryptografische Schritte:

// Anfällig: HMAC-Implementierung ohne iteratives Hashing für lange Nachrichten
module vulnerable_hmac_engine(
    input wire clk,
    input wire rst,
    input wire [255:0] key,
    input wire [511:0] message,  // Kann nur 512 Bits verarbeiten!
    input wire start,
    output reg [255:0] hmac_out,
    output reg done
);

    // Anfällig: Kann keine Nachrichten länger als 512 Bits verarbeiten
    // HMAC erfordert iteratives Hashing für längere Nachrichten

    reg [255:0] inner_hash;
    reg [255:0] outer_hash;

    always @(posedge clk) begin
        if (rst) begin
            done <= 0;
        end else if (start) begin
            // Schlüssel mit ipad XOR-verknüpfen
            // Hash von (key XOR ipad) || message
            inner_hash <= sha256(key ^ 256'h3636...36, message);

            // Schlüssel mit opad XOR-verknüpfen
            // Hash von (key XOR opad) || inner_hash
            outer_hash <= sha256(key ^ 256'h5c5c...5c, inner_hash);

            hmac_out <= outer_hash;
            done <= 1;

            // Anfällig: Nachrichten > 512 Bits werden abgeschnitten oder verursachen Fehler!
            // Die iterative Funktion zur Verarbeitung von Nachrichtenblöcken fehlt
        end
    end
endmodule
// Anfällig: Fehlende Nachrichtenpolsterung in Hash-Funktion
#include <string.h>
#include <stdint.h>

// Anfällig: SHA-256-Implementierung ohne Padding-Schritt
void vulnerable_sha256(const uint8_t *message, size_t len, uint8_t *hash) {
    uint32_t state[8] = {
        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
        0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
    };

    // Vollständige 512-Bit-Blöcke verarbeiten
    size_t blocks = len / 64;
    for (size_t i = 0; i < blocks; i++) {
        sha256_transform(state, message + (i * 64));
    }

    // Anfällig: Padding-Schritt fehlt!
    // SHA-256 erfordert:
    // 1. Bit '1' an Nachricht anhängen
    // 2. Nullen anhängen bis Nachricht 448 Bits mod 512 ist
    // 3. Originale Nachrichtenlänge als 64-Bit Big-Endian anhängen

    // Kopiert nur den Zustand ohne ordnungsgemäße Finalisierung
    memcpy(hash, state, 32);

    // Ergebnis ist falsch für Nachrichten, die nicht genau ein Vielfaches von 64 Bytes sind!
}

// Anfällig: RSA-Signatur ohne ordnungsgemäßes Padding
int vulnerable_rsa_sign(const uint8_t *message, size_t msg_len,
                        const RSA_KEY *key, uint8_t *signature) {

    uint8_t hash[32];
    sha256(message, msg_len, hash);

    // Anfällig: Direkte modulare Exponentiation ohne Padding!
    // PKCS#1 v1.5 oder PSS-Padding fehlt
    // Dies ermöglicht Signaturfälschungsangriffe

    // Sollte sein: padded = EMSA_PKCS1_v1_5_ENCODE(hash, key_size)
    // Stattdessen nur roher Hash verwendet

    bignum_mod_exp(signature, hash, key->d, key->n);

    return 0;
}

// Anfällig: Fehlende Schlüsselableitungsfunktion
int vulnerable_derive_key(const char *password, uint8_t *key) {
    // Anfällig: Direkter Hash des Passworts
    // Ordnungsgemäße KDF fehlt (PBKDF2, scrypt, argon2)

    sha256(password, strlen(password), key);

    // Kein Salt! Keine Iterationen! Anfällig für Rainbow Tables!
    return 0;
}
# Anfällig: Fehlende Authentifizierungs-Tag-Verifizierung
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

def vulnerable_decrypt_gcm(key, nonce, ciphertext, tag):
    # Anfällig: Entschlüsseln ohne Verifizierung des Authentifizierungs-Tags!

    cipher = Cipher(algorithms.AES(key), modes.GCM(nonce))
    decryptor = cipher.decryptor()

    # Nur entschlüsseln ohne zu authentifizieren
    plaintext = decryptor.update(ciphertext)

    # Anfällig: Tag wird nie verifiziert!
    # Sollte aufrufen: decryptor.finalize_with_tag(tag)
    # Das Fehlen dieses Schritts ermöglicht, dass modifizierter Geheimtext akzeptiert wird

    return plaintext  # Könnten gefälschte Daten sein!

# Anfällig: Challenge-Response ohne Challenge-Verifizierung
def vulnerable_authenticate(client_socket, expected_public_key):
    # Challenge generieren
    challenge = os.urandom(32)
    client_socket.send(challenge)

    # Antwort empfangen
    response = client_socket.recv(256)

    # Anfällig: Fehlende Verifizierung dass Antwort für DIESE Challenge ist!
    # Sollte verifizieren: signature = sign(challenge, private_key)
    # Prüft stattdessen nur ob irgendeine gültige Signatur

    try:
        expected_public_key.verify(response, b'anything')  # Falsch!
        return True
    except:
        return False

Korrigierter Code (Verilog/C)

// Korrigiert: HMAC-Implementierung mit iterativem Hashing
module secure_hmac_engine(
    input wire clk,
    input wire rst,
    input wire [255:0] key,
    input wire [7:0] message_byte,
    input wire message_valid,
    input wire message_last,
    input wire start,
    output reg [255:0] hmac_out,
    output reg done
);

    // Korrigiert: Implementiert iteratives Hashing für Nachrichten beliebiger Länge

    reg [511:0] block_buffer;
    reg [6:0] block_offset;
    reg [255:0] hash_state;
    reg processing_inner;

    // SHA-256 Initialisierungszustand
    localparam [255:0] SHA256_INIT = {
        32'h6a09e667, 32'hbb67ae85, 32'h3c6ef372, 32'ha54ff53a,
        32'h510e527f, 32'h9b05688c, 32'h1f83d9ab, 32'h5be0cd19
    };

    always @(posedge clk) begin
        if (rst) begin
            done <= 0;
            block_offset <= 0;
            hash_state <= SHA256_INIT;
        end else if (start) begin
            // Mit (key XOR ipad) initialisieren
            block_buffer[511:256] <= key ^ 256'h3636363636...;
            block_offset <= 32;
            processing_inner <= 1;
        end else if (message_valid && processing_inner) begin
            // Korrigiert: Nachrichtenbytes akkumulieren
            block_buffer[511 - (block_offset * 8) -: 8] <= message_byte;
            block_offset <= block_offset + 1;

            // Korrigiert: Vollständige Blöcke iterativ verarbeiten
            if (block_offset == 63) begin
                hash_state <= sha256_transform(hash_state, block_buffer);
                block_offset <= 0;
            end
        end else if (message_last && processing_inner) begin
            // Korrigiert: Ordnungsgemäßes Padding anwenden
            hash_state <= sha256_finalize(hash_state, block_buffer,
                                          block_offset, total_length);

            // Äußeren Hash starten
            processing_inner <= 0;
            // (key XOR opad) || inner_hash verarbeiten
        end
        // ... äußeren Hash ähnlich vervollständigen
    end
endmodule
// Korrigiert: SHA-256 mit ordnungsgemäßem Padding
#include <string.h>
#include <stdint.h>

void secure_sha256(const uint8_t *message, size_t len, uint8_t *hash) {
    uint32_t state[8] = {
        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
        0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
    };

    // Vollständige 512-Bit-Blöcke verarbeiten
    size_t blocks = len / 64;
    for (size_t i = 0; i < blocks; i++) {
        sha256_transform(state, message + (i * 64));
    }

    // Korrigiert: Ordnungsgemäße Padding-Implementierung
    uint8_t padding_block[128] = {0};  // Kann 2 Blöcke benötigen
    size_t remaining = len % 64;

    // Verbleibende Bytes kopieren
    memcpy(padding_block, message + (blocks * 64), remaining);

    // Korrigiert: Bit '1' anhängen
    padding_block[remaining] = 0x80;

    // Korrigiert: Bestimmen ob ein oder zwei Padding-Blöcke nötig sind
    size_t padding_blocks;
    if (remaining >= 56) {
        padding_blocks = 2;  // Extra-Block für Länge nötig
    } else {
        padding_blocks = 1;
    }

    // Korrigiert: Länge als 64-Bit Big-Endian am Ende anhängen
    uint64_t bit_len = len * 8;
    size_t len_offset = (padding_blocks * 64) - 8;
    for (int i = 0; i < 8; i++) {
        padding_block[len_offset + i] = (bit_len >> (56 - i * 8)) & 0xff;
    }

    // Padding-Block(s) verarbeiten
    for (size_t i = 0; i < padding_blocks; i++) {
        sha256_transform(state, padding_block + (i * 64));
    }

    // Korrigiert: Ordnungsgemäße Ausgabeformatierung
    for (int i = 0; i < 8; i++) {
        hash[i*4 + 0] = (state[i] >> 24) & 0xff;
        hash[i*4 + 1] = (state[i] >> 16) & 0xff;
        hash[i*4 + 2] = (state[i] >> 8) & 0xff;
        hash[i*4 + 3] = state[i] & 0xff;
    }
}

// Korrigiert: RSA-Signatur mit ordnungsgemäßem Padding
int secure_rsa_sign(const uint8_t *message, size_t msg_len,
                    const RSA_KEY *key, uint8_t *signature) {

    uint8_t hash[32];
    sha256(message, msg_len, hash);

    // Korrigiert: PKCS#1 PSS-Padding anwenden
    uint8_t padded[key->size];
    if (pkcs1_pss_encode(hash, sizeof(hash), key->size, padded) != 0) {
        return -1;
    }

    // Die ordnungsgemäß gepolsterte Nachricht signieren
    bignum_mod_exp(signature, padded, key->d, key->n);

    return 0;
}

// Korrigiert: Ordnungsgemäße Schlüsselableitung
int secure_derive_key(const char *password, const uint8_t *salt,
                      size_t salt_len, uint8_t *key) {

    // Korrigiert: Ordnungsgemäße KDF mit Salt und Iterationen verwenden
    return PKCS5_PBKDF2_HMAC(
        password, strlen(password),
        salt, salt_len,
        100000,  // Iterationen
        EVP_sha256(),
        32,  // Schlüssellänge
        key
    );
}
# Korrigiert: Ordnungsgemäße GCM-Entschlüsselung mit Tag-Verifizierung
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

def secure_decrypt_gcm(key, nonce, ciphertext, tag):
    cipher = Cipher(algorithms.AES(key), modes.GCM(nonce, tag))
    decryptor = cipher.decryptor()

    plaintext = decryptor.update(ciphertext)

    # Korrigiert: Authentifizierungs-Tag während Finalisierung verifizieren
    # Dies wird InvalidTag auslösen wenn Authentifizierung fehlschlägt
    decryptor.finalize()

    return plaintext

# Korrigiert: Challenge-Response mit ordnungsgemäßer Verifizierung
def secure_authenticate(client_socket, expected_public_key):
    # Einzigartige Challenge generieren
    challenge = os.urandom(32)
    client_socket.send(challenge)

    # Antwort empfangen
    signature = client_socket.recv(256)

    # Korrigiert: Verifizieren dass Signatur spezifisch für DIESE Challenge ist
    try:
        expected_public_key.verify(
            signature,
            challenge,  # Die tatsächlichen Challenge-Daten
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256()
        )
        return True
    except InvalidSignature:
        return False

Die Korrektur implementiert alle erforderlichen kryptografischen Schritte einschließlich Padding, Verifizierung und ordnungsgemäßer Schlüsselableitung.


Ausgenutzt in der Praxis

SSH-Authentifizierungs-Bypass (Verschiedene, 2001)

CVE-2001-1585 dokumentierte eine SSH-Implementierung ohne Challenge-Response-Verifizierung, die Authentifizierungs-Bypass unter Verwendung nur des öffentlichen Schlüssels ermöglichte.

Unvollständige HMAC-Implementierungen (Verschiedene Hardware)

Hardware-Implementierungen mit begrenztem Speicher haben HMAC-Engines ausgeliefert, die keine Nachrichten verarbeiten können, die größer als ihre Puffergröße sind, was die Authentifizierung schwächt.


Tools zum Testen/Ausnutzen

  • Cryptographic Test Vectors — NIST-Testvektoren zur Validierung von Implementierungen.

  • Wycheproof — Googles Testvektoren für kryptografische Bibliotheken.

  • Cryptofuzz — Differentielles Fuzzing für kryptografische Bibliotheken.


CVE-Beispiele

  • CVE-2001-1585 — Fehlender Challenge-Response-Schritt in SSH-Authentifizierung.

Referenzen

  1. MITRE Corporation. "CWE-325: Missing Cryptographic Step." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/325.html

  2. NIST. "Secure Hash Standard." FIPS 180-4. https://csrc.nist.gov/publications/detail/fips/180/4/final

  3. RFC 2104. "HMAC: Keyed-Hashing for Message Authentication." https://tools.ietf.org/html/rfc2104