Unsachgemäße Validierung der Konsistenz innerhalb der Eingabe

Beschreibung

Unsachgemäße Validierung der Konsistenz innerhalb der Eingabe tritt auf, wenn ein Produkt Eingaben mit mehreren Elementen oder Feldern erhält, die miteinander übereinstimmen sollten, aber diese Konsistenz nicht validiert oder falsch validiert. Komplexe strukturierte Eingaben enthalten oft voneinander abhängige Felder -- wie ein Zählfeld gefolgt von der angegebenen Anzahl von Elementen, oder ein Größenfeld, das mit der tatsächlichen Datenlänge übereinstimmen muss. Wenn diese Eingaben inkonsistent sind, können Angreifer unerwartete Fehler auslösen, falsche Aktionen verursachen oder ruhende Schwachstellen ausnutzen.

Risiko

Inkonsistente Eingabevalidierung hat schwerwiegende Sicherheitsauswirkungen. Buffer Overflows durch Größenabweichungen. Datenbeschädigung durch Zählfehler. Denial of Service möglich. Logikfehler können auftreten. Speicherbeschädigungsangriffe werden ermöglicht. Sicherheitsumgehungen möglich. Parsing-Schwachstellen aufgedeckt. Anwendungsabstürze wahrscheinlich.

Lösung

Implementieren Sie eine Allowlist-basierte Validierung, die strikte Konformität mit Spezifikationen erfordert. Validieren Sie alle relevanten Eigenschaften: Länge, Typ, Wertebereiche, Syntax und Konsistenz zwischen Feldern. Lehnen Sie Eingaben ab, die die Validierung nicht bestehen, anstatt Reparaturen zu versuchen. Überprüfen Sie, dass Zählfelder mit der tatsächlichen Elementanzahl übereinstimmen. Stellen Sie sicher, dass Größenfelder mit der tatsächlichen Datengröße übereinstimmen. Kreuzvalidieren Sie verwandte Felder.

Häufige Auswirkungen

AuswirkungDetails
SonstigesBereich: Sonstiges

Auswirkung variiert je nachdem, wie inkonsistente Eingaben verarbeitet werden.
IntegritätBereich: Integrität

Inkonsistente Daten können den Anwendungszustand beschädigen.
VerfügbarkeitBereich: Verfügbarkeit

Nicht übereinstimmende Zähler/Größen können Abstürze verursachen.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Keine Konsistenzvalidierung zwischen Feldern

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    uint32_t count;
    uint32_t items[100];  // Festes Maximum, aber 'count' könnte größer sein
} vulnerable_packet_t;

// VERWUNDBAR: Zähler nicht gegen tatsächliche Elemente validiert
void vulnerable_process_packet(const uint8_t* data, size_t data_len) {
    // VERWUNDBAR: Zähler ohne Validierung lesen
    uint32_t count;
    memcpy(&count, data, sizeof(count));

    // VERWUNDBAR: Keine Prüfung, ob count mit verfügbaren Daten übereinstimmt
    // Wenn count > (data_len - 4) / 4, lesen wir über den Puffer hinaus
    for (uint32_t i = 0; i < count; i++) {
        uint32_t item;
        memcpy(&item, data + 4 + i * 4, sizeof(item));
        process_item(item);  // Buffer Over-Read wenn count zu groß
    }
}

// VERWUNDBAR: Größenfeld nicht gegen Paketgröße validiert
typedef struct {
    uint32_t total_size;
    uint32_t header_size;
    uint32_t data_size;
    uint8_t header[256];
    uint8_t data[1024];
} message_t;

void vulnerable_process_message(const message_t* msg, size_t actual_size) {
    // VERWUNDBAR: Keine Validierung der Größenfelder

    // total_size könnte inkonsistent mit actual_size sein
    // header_size + data_size könnte != total_size sein

    // VERWUNDBAR: Bereitgestellte Größen ohne Validierung verwenden
    process_header(msg->header, msg->header_size);
    process_data(msg->data, msg->data_size);
}

// VERWUNDBAR: Start-/Endblock-Inkonsistenz
typedef struct {
    uint32_t start_block;
    uint32_t end_block;
    uint8_t block_data[4096];
} block_range_t;

void vulnerable_process_blocks(const block_range_t* range) {
    // VERWUNDBAR: Keine Prüfung, ob start < end
    // Wenn start > end, könnte die Schleife unterlaufen oder falsch überspringen

    for (uint32_t i = range->start_block; i <= range->end_block; i++) {
        // Wenn start=100, end=50, wird dies nie ausgeführt (oder umgebrochen)
        process_block(i, range->block_data);
    }
}
# VERWUNDBAR: Python mit inkonsistenter Feldbehandlung

import struct

# VERWUNDBAR: Längenfeld nicht validiert
def vulnerable_parse_tlv(data):
    """Type-Length-Value-Struktur parsen."""
    offset = 0
    results = []

    while offset < len(data):
        # Typ und Länge lesen
        tlv_type = data[offset]
        length = struct.unpack('>H', data[offset+1:offset+3])[0]

        # VERWUNDBAR: Keine Prüfung, ob Länge mit verbleibenden Daten konsistent ist
        value = data[offset+3:offset+3+length]

        # Wenn length > verbleibende Daten, wird value stillschweigend gekürzt
        results.append({'type': tlv_type, 'length': length, 'value': value})

        offset += 3 + length

    return results

# VERWUNDBAR: Array-Zähler nicht validiert
def vulnerable_process_items(data):
    """Elemente mit Zähler-Präfix verarbeiten."""
    count = struct.unpack('>I', data[:4])[0]

    # VERWUNDBAR: Keine Validierung, dass Daten 'count' Elemente enthalten
    items = []
    offset = 4

    for i in range(count):
        # Wirft IndexError wenn nicht genug Daten vorhanden
        # Oder liest teilweise/falsche Daten
        item_size = struct.unpack('>I', data[offset:offset+4])[0]
        item_data = data[offset+4:offset+4+item_size]
        items.append(item_data)
        offset += 4 + item_size

    return items

# VERWUNDBAR: Prüfsumme nicht validiert
def vulnerable_process_packet(packet):
    """Paket mit Prüfsumme verarbeiten."""
    data = packet['data']
    checksum = packet['checksum']

    # VERWUNDBAR: Prüfsumme nicht verifiziert
    # Angreifer kann Daten und Prüfsumme unabhängig modifizieren

    process_data(data)  # Potenziell beschädigte Daten verarbeiten
// VERWUNDBAR: Java mit inkonsistenter Eingabebehandlung

public class VulnerableParser {

    // VERWUNDBAR: Deklarierte Länge nicht gegen tatsächliche validiert
    public void processChunk(byte[] data) {
        int offset = 0;

        while (offset < data.length) {
            // Deklarierte Chunk-Größe lesen
            int chunkSize = ByteBuffer.wrap(data, offset, 4).getInt();
            offset += 4;

            // VERWUNDBAR: Keine Validierung, dass chunkSize Bytes verfügbar sind
            byte[] chunk = new byte[chunkSize];

            // ArrayIndexOutOfBoundsException oder liest falsche Daten
            System.arraycopy(data, offset, chunk, 0, chunkSize);
            offset += chunkSize;

            processChunkData(chunk);
        }
    }

    // VERWUNDBAR: Zählerfeld-Inkonsistenz
    public List<String> parseStringList(DataInputStream input) throws IOException {
        int count = input.readInt();  // Deklarierter Zähler
        List<String> result = new ArrayList<>();

        // VERWUNDBAR: Dem Zählerfeld vertrauen
        for (int i = 0; i < count; i++) {
            int length = input.readInt();
            byte[] bytes = new byte[length];

            // VERWUNDBAR: Keine Validierung der Länge gegen verfügbare Daten
            input.readFully(bytes);
            result.add(new String(bytes, StandardCharsets.UTF_8));
        }

        return result;
    }
}

Sichere Lösung

// SICHER: Ordnungsgemäße Konsistenzvalidierung

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

// SICHER: Zähler gegen verfügbare Daten validieren
bool secure_process_packet(const uint8_t* data, size_t data_len) {
    // SICHER: Mindestgrößenprüfung
    if (data_len < sizeof(uint32_t)) {
        log_error("Paket zu klein für Zählerfeld");
        return false;
    }

    // Zähler lesen
    uint32_t count;
    memcpy(&count, data, sizeof(count));

    // SICHER: Zähler gegen tatsächliche Daten validieren
    size_t expected_size = sizeof(uint32_t) + count * sizeof(uint32_t);
    if (expected_size > data_len) {
        log_error("Zähler %u überschreitet verfügbare Daten (haben %zu, brauchen %zu)",
                 count, data_len, expected_size);
        return false;
    }

    // SICHER: Auch auf Zählerüberlauf prüfen
    if (count > (SIZE_MAX - sizeof(uint32_t)) / sizeof(uint32_t)) {
        log_error("Zählerüberlauf");
        return false;
    }

    // Jetzt sicher zu verarbeiten
    for (uint32_t i = 0; i < count; i++) {
        uint32_t item;
        memcpy(&item, data + 4 + i * 4, sizeof(item));
        process_item(item);
    }

    return true;
}

// SICHER: Alle Größenfelder auf Konsistenz validieren
typedef struct {
    uint32_t total_size;
    uint32_t header_size;
    uint32_t data_size;
    uint8_t header[256];
    uint8_t data[1024];
} message_t;

bool secure_process_message(const message_t* msg, size_t actual_size) {
    // SICHER: actual_size ist mindestens Minimum
    if (actual_size < offsetof(message_t, header)) {
        log_error("Nachricht zu klein");
        return false;
    }

    // SICHER: total_size stimmt mit actual_size überein
    if (msg->total_size != actual_size) {
        log_error("Gesamtgröße stimmt nicht überein: deklariert %u, tatsächlich %zu",
                 msg->total_size, actual_size);
        return false;
    }

    // SICHER: header_size validieren
    if (msg->header_size > sizeof(msg->header)) {
        log_error("Header-Größe überschreitet Puffer");
        return false;
    }

    // SICHER: data_size validieren
    if (msg->data_size > sizeof(msg->data)) {
        log_error("Datengröße überschreitet Puffer");
        return false;
    }

    // SICHER: Größen sind konsistent
    size_t computed_size = offsetof(message_t, header) +
                           msg->header_size + msg->data_size;
    if (computed_size != msg->total_size) {
        log_error("Größenfelder inkonsistent: header=%u, data=%u, total=%u",
                 msg->header_size, msg->data_size, msg->total_size);
        return false;
    }

    // Jetzt sicher zu verarbeiten
    process_header(msg->header, msg->header_size);
    process_data(msg->data, msg->data_size);
    return true;
}

// SICHER: Start-/End-Konsistenz validieren
typedef struct {
    uint32_t start_block;
    uint32_t end_block;
    uint8_t block_data[4096];
} block_range_t;

bool secure_process_blocks(const block_range_t* range) {
    // SICHER: start <= end validieren
    if (range->start_block > range->end_block) {
        log_error("Ungültiger Blockbereich: start %u > end %u",
                 range->start_block, range->end_block);
        return false;
    }

    // SICHER: Bereich ist angemessen
    uint32_t count = range->end_block - range->start_block + 1;
    if (count > MAX_BLOCKS) {
        log_error("Blockbereich zu groß: %u Blöcke", count);
        return false;
    }

    for (uint32_t i = range->start_block; i <= range->end_block; i++) {
        process_block(i, range->block_data);
    }

    return true;
}
# SICHER: Python mit Konsistenzvalidierung

import struct
from typing import List, Dict, Any, Optional

# SICHER: TLV-Parsing mit Längenvalidierung
def secure_parse_tlv(data: bytes) -> List[Dict[str, Any]]:
    """Type-Length-Value-Struktur mit Validierung parsen."""
    offset = 0
    results = []

    while offset < len(data):
        # SICHER: Minimale Header-Größe prüfen
        if offset + 3 > len(data):
            raise ValueError(f"Unvollständiger TLV-Header bei Offset {offset}")

        # Typ und Länge lesen
        tlv_type = data[offset]
        length = struct.unpack('>H', data[offset+1:offset+3])[0]

        # SICHER: Länge gegen verbleibende Daten validieren
        if offset + 3 + length > len(data):
            raise ValueError(
                f"TLV-Länge {length} überschreitet verbleibende Daten "
                f"(offset={offset}, data_len={len(data)})"
            )

        value = data[offset+3:offset+3+length]

        # SICHER: Erwartete Menge erhalten
        if len(value) != length:
            raise ValueError(f"TLV-Wert gekürzt: erwartet {length}, erhalten {len(value)}")

        results.append({'type': tlv_type, 'length': length, 'value': value})
        offset += 3 + length

    return results

# SICHER: Array-Zähler-Validierung
def secure_process_items(data: bytes, max_count: int = 1000) -> List[bytes]:
    """Elemente mit validiertem Zähler-Präfix verarbeiten."""
    if len(data) < 4:
        raise ValueError("Daten zu kurz für Zählerfeld")

    count = struct.unpack('>I', data[:4])[0]

    # SICHER: Zähler ist angemessen
    if count > max_count:
        raise ValueError(f"Zähler {count} überschreitet Maximum {max_count}")

    items = []
    offset = 4

    for i in range(count):
        # SICHER: Element-Header prüfen
        if offset + 4 > len(data):
            raise ValueError(f"Unvollständiger Element-Header bei Index {i}")

        item_size = struct.unpack('>I', data[offset:offset+4])[0]

        # SICHER: item_size gegen verbleibende Daten validieren
        if offset + 4 + item_size > len(data):
            raise ValueError(
                f"Element {i} Größe {item_size} überschreitet verbleibende Daten"
            )

        item_data = data[offset+4:offset+4+item_size]
        items.append(item_data)
        offset += 4 + item_size

    # SICHER: Alle Daten verbraucht (kein nachfolgendes Müll)
    if offset != len(data):
        raise ValueError(f"Unerwartete nachfolgende Daten: {len(data) - offset} Bytes")

    return items

# SICHER: Prüfsummenvalidierung
import hashlib

def secure_process_packet(packet: Dict[str, Any]) -> bool:
    """Paket mit Prüfsummenvalidierung verarbeiten."""
    if 'data' not in packet or 'checksum' not in packet:
        raise ValueError("Fehlende erforderliche Felder")

    data = packet['data']
    expected_checksum = packet['checksum']

    # SICHER: Prüfsumme vor Verarbeitung verifizieren
    actual_checksum = hashlib.sha256(data).hexdigest()

    if actual_checksum != expected_checksum:
        raise ValueError(
            f"Prüfsumme stimmt nicht überein: erwartet {expected_checksum}, "
            f"berechnet {actual_checksum}"
        )

    # Jetzt sicher, verifizierte Daten zu verarbeiten
    process_data(data)
    return True
// SICHER: Java mit Konsistenzvalidierung

public class SecureParser {

    private static final int MAX_CHUNK_SIZE = 1024 * 1024;  // 1 MB

    // SICHER: Deklarierte Länge gegen tatsächliche validieren
    public void processChunk(byte[] data) throws ValidationException {
        int offset = 0;

        while (offset < data.length) {
            // SICHER: Header passt
            if (offset + 4 > data.length) {
                throw new ValidationException(
                    "Unvollständiger Chunk-Header bei Offset " + offset);
            }

            // Deklarierte Chunk-Größe lesen
            int chunkSize = ByteBuffer.wrap(data, offset, 4).getInt();
            offset += 4;

            // SICHER: Chunk-Größe validieren
            if (chunkSize < 0) {
                throw new ValidationException("Negative Chunk-Größe: " + chunkSize);
            }

            if (chunkSize > MAX_CHUNK_SIZE) {
                throw new ValidationException(
                    "Chunk-Größe " + chunkSize + " überschreitet Maximum " + MAX_CHUNK_SIZE);
            }

            // SICHER: Chunk-Daten verfügbar
            if (offset + chunkSize > data.length) {
                throw new ValidationException(
                    "Chunk-Größe " + chunkSize + " überschreitet verbleibende Daten " +
                    (data.length - offset));
            }

            byte[] chunk = new byte[chunkSize];
            System.arraycopy(data, offset, chunk, 0, chunkSize);
            offset += chunkSize;

            processChunkData(chunk);
        }
    }

    // SICHER: Zählerfeld-Validierung
    public List<String> parseStringList(DataInputStream input, int maxCount)
            throws IOException, ValidationException {

        int count = input.readInt();

        // SICHER: Zähler validieren
        if (count < 0) {
            throw new ValidationException("Negativer Zähler: " + count);
        }

        if (count > maxCount) {
            throw new ValidationException(
                "Zähler " + count + " überschreitet Maximum " + maxCount);
        }

        List<String> result = new ArrayList<>(count);

        for (int i = 0; i < count; i++) {
            // SICHER: Zeichenkettenlänge validieren
            int length = input.readInt();

            if (length < 0) {
                throw new ValidationException(
                    "Negative Zeichenkettenlänge bei Index " + i);
            }

            if (length > MAX_STRING_LENGTH) {
                throw new ValidationException(
                    "Zeichenkettenlänge " + length + " überschreitet Maximum bei Index " + i);
            }

            byte[] bytes = new byte[length];

            // SICHER: Erwartete Menge gelesen
            int read = input.read(bytes);
            if (read != length) {
                throw new ValidationException(
                    "Erwartet " + length + " Bytes, erhalten " + read);
            }

            result.add(new String(bytes, StandardCharsets.UTF_8));
        }

        return result;
    }
}

CVE-Beispiele

  • CVE-2018-16733: Produkt validiert nicht, dass der Startblock vor dem Endblock erscheint, was Verarbeitungsfehler verursacht.
  • CVE-2006-3790: Größenfeld, das inkonsistent mit der Paketgröße ist, führt zu Buffer Over-Read.
  • CVE-2008-4114: Systemabsturz durch Offset-Wert, der inkonsistent mit der Paketgröße ist.

Verwandte CWEs

  • CWE-20: Unsachgemäße Eingabevalidierung (übergeordnet)
  • CWE-1215: Datenvalidierungsprobleme (Kategorie)
  • CWE-130: Unsachgemäße Behandlung von Längenparameter-Inkonsistenz (verwandt)
  • CWE-805: Pufferzugriff mit falschem Längenwert (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1288: Improper Validation of Consistency within Input." https://cwe.mitre.org/data/definitions/1288.html
  2. OWASP. "Input Validation Cheat Sheet"
  3. CERT. "Secure Coding Guidelines"