Unsachgemäße Validierung unsicherer Äquivalenz in der Eingabe

Beschreibung

Unsachgemäße Validierung unsicherer Äquivalenz in der Eingabe tritt auf, wenn ein Produkt eine Eingabe als Ressourcenbezeichner akzeptiert, aber nicht validiert, ob sie einem potenziell gefährlichen Wert äquivalent ist. Angreifer können manchmal Eingabevalidierungsschemata umgehen, indem sie Eingaben finden, die sicher erscheinen, aber gefährlich sind, wenn sie auf einer niedrigeren Ebene oder von einer nachgelagerten Komponente verarbeitet werden. Ein praktisches Beispiel: XSS-Filter, die groß-/kleinschreibungssensitiven Abgleich für <script>-Tags verwenden, können mit <ScrIpT> umgangen werden, da HTML-Rendering groß-/kleinschreibungsunempfindlich ist.

Risiko

Unsichere Äquivalenzumgehung hat schwerwiegende Sicherheitsauswirkungen. Eingabefilter können umgangen werden. XSS-Angriffe können erfolgreich sein. Path Traversal ist möglich. Zugriffskontrollen umgehbar. Injection-Angriffe werden ermöglicht. Sicherheitsrichtlinien unwirksam. Denylists werden nutzlos. Nachgelagerte Verarbeitung wird ausgenutzt.

Lösung

Verwenden Sie die Validierung nach dem Prinzip "bekannt Gutes akzeptieren": Pflegen Sie strikte Allowlists akzeptabler Eingaben, die den Spezifikationen entsprechen. Lehnen Sie nicht-konforme Daten ab. Berücksichtigen Sie Länge, Typ, Wertebereiche, Syntax und Geschäftslogik. Normalisieren Sie Eingaben vor der Validierung, wo angemessen. Verwenden Sie groß-/kleinschreibungsunempfindliche Vergleiche für groß-/kleinschreibungsunempfindliche Protokolle. Denylists allein sind unzureichend - sie können nicht alle Äquivalenzen abdecken.

Häufige Auswirkungen

AuswirkungDetails
SonstigesBereich: Sonstiges

Sicherheitsumgehung abhängig von der Ausnutzungsmethode.
ZugriffskontrolleBereich: Zugriffskontrolle

Filter und Einschränkungen können umgangen werden.
IntegritätBereich: Integrität

Bösartige Eingaben können nachgelagerte Komponenten erreichen.

Beispielcode und Lösung

Verwundbarer Code

# VERWUNDBAR: Groß-/kleinschreibungssensitiver XSS-Filter

import re

# VERWUNDBAR: Groß-/kleinschreibungssensitive Blockliste
DANGEROUS_TAGS = ['<script>', '</script>', '<iframe>', '<object>']

def vulnerable_xss_filter(input_html):
    """XSS filtern - VERWUNDBAR gegenüber Groß-/Kleinschreibungsvariation."""
    result = input_html

    # VERWUNDBAR: Groß-/kleinschreibungssensitiver Abgleich
    for tag in DANGEROUS_TAGS:
        result = result.replace(tag, '')

    return result

    # Angreifer verwendet: <ScRiPt>alert(1)</sCrIpT>
    # Filter erkennt gemischte Schreibweise nicht
    # Browser führt es trotzdem aus (HTML ist groß-/kleinschreibungsunempfindlich)

# VERWUNDBAR: Groß-/kleinschreibungssensitive Dateierweiterungsprüfung
def vulnerable_file_upload(filename):
    """Dateierweiterung prüfen - VERWUNDBAR gegenüber Groß-/Kleinschreibungsumgehung."""
    BLOCKED_EXTENSIONS = ['.exe', '.bat', '.cmd', '.ps1']

    # VERWUNDBAR: Groß-/kleinschreibungssensitiver Vergleich
    for ext in BLOCKED_EXTENSIONS:
        if filename.endswith(ext):
            return False

    return True  # Upload erlauben

    # Angreifer lädt hoch: malware.EXE oder malware.Exe
    # Filter erkennt Großbuchstaben-Erweiterungen nicht

# VERWUNDBAR: URL-Kodierung nicht berücksichtigt
def vulnerable_path_check(path):
    """Auf Directory Traversal prüfen - VERWUNDBAR gegenüber Kodierungsumgehung."""
    # VERWUNDBAR: Prüft nur literales "../"
    if '../' in path:
        return False

    return True

    # Angreifer verwendet: %2e%2e%2f (URL-kodiertes ../)
    # Oder: ..%2f (teilweise Kodierung)
    # Oder: ..\ (Backslash unter Windows)
// VERWUNDBAR: JavaScript mit Äquivalenzproblemen

// VERWUNDBAR: Groß-/kleinschreibungssensitive Hostname-Prüfung
function vulnerableCheckRedirect(url) {
    // VERWUNDBAR: Groß-/kleinschreibungssensitiver Vergleich
    if (url.includes('malicious.com')) {
        return false;
    }
    return true;

    // Angreifer verwendet: MALICIOUS.COM oder Malicious.Com
    // DNS ist groß-/kleinschreibungsunempfindlich, Weiterleitung gelingt
}

// VERWUNDBAR: Leerzeichen-Äquivalenz nicht berücksichtigt
function vulnerableValidateUrl(url) {
    const allowedHosts = ['example.com', 'trusted.com'];

    // URL parsen
    const parsed = new URL(url);

    // VERWUNDBAR: Berücksichtigt keine Leerzeichen-Varianten
    if (!allowedHosts.includes(parsed.hostname)) {
        return false;
    }

    return true;

    // Angreifer verwendet: "https://evil.com%20.example.com"
    // Oder kodierte Leerzeichen, die das Parsing beeinflussen
}

// VERWUNDBAR: Unicode-Äquivalenz
function vulnerableUsernameCheck(username) {
    const blockedUsers = ['admin', 'root', 'administrator'];

    // VERWUNDBAR: Normalisiert kein Unicode
    if (blockedUsers.includes(username.toLowerCase())) {
        return false;
    }

    return true;

    // Angreifer verwendet: "ɑdmin" (lateinisches Alpha statt 'a')
    // Oder: "аdmin" (kyrillisches 'а' statt lateinischem 'a')
}
// VERWUNDBAR: C mit Äquivalenzumgehungsproblemen

#include <string.h>
#include <ctype.h>

// VERWUNDBAR: Groß-/kleinschreibungssensitive Befehlsprüfung
int vulnerable_check_command(const char* cmd) {
    // VERWUNDBAR: Blockiert nur exakte Kleinbuchstabenübereinstimmung
    if (strcmp(cmd, "shutdown") == 0 ||
        strcmp(cmd, "reboot") == 0 ||
        strcmp(cmd, "format") == 0) {
        return 0;  // Blockiert
    }
    return 1;  // Erlaubt

    // Angreifer verwendet: "SHUTDOWN", "Shutdown", "sHuTdOwN"
}

// VERWUNDBAR: Pfadprüfung ohne Normalisierung
int vulnerable_check_path(const char* path) {
    // VERWUNDBAR: Prüft nur literales Muster
    if (strstr(path, "..") != NULL) {
        return 0;  // Blockiert
    }
    if (strstr(path, "/etc/") != NULL) {
        return 0;  // Blockiert
    }
    return 1;

    // Angreifer verwendet:
    // - "....//etc/passwd" (doppelte Punkte)
    // - "/etc//passwd" (doppelte Schrägstriche)
    // - "/etc/./passwd" (aktuelle Verzeichnisreferenz)
}

// VERWUNDBAR: Erweiterungsprüfung ohne Groß-/Kleinschreibungsnormalisierung
int vulnerable_check_extension(const char* filename) {
    const char* ext = strrchr(filename, '.');
    if (ext == NULL) return 0;

    // VERWUNDBAR: Groß-/kleinschreibungssensitiver Vergleich
    if (strcmp(ext, ".php") == 0 ||
        strcmp(ext, ".asp") == 0 ||
        strcmp(ext, ".jsp") == 0) {
        return 0;  // Blockiert
    }
    return 1;

    // Angreifer lädt hoch: shell.PHP, shell.Php, shell.pHp
}

Sichere Lösung

# SICHER: Ordnungsgemäße Äquivalenzbehandlung

import re
import html
import urllib.parse
import unicodedata

# SICHER: Groß-/kleinschreibungsunempfindlicher XSS-Filter mit Normalisierung
def secure_xss_filter(input_html):
    """XSS mit ordnungsgemäßer Groß-/Kleinschreibungsbehandlung filtern."""
    # SICHER: Allowlist-Ansatz statt Denylist verwenden
    # Nur bestimmte sichere Tags erlauben

    # Ansatz 1: Alles HTML escapen
    escaped = html.escape(input_html)
    return escaped

    # Ansatz 2: Wenn HTML benötigt, ordnungsgemäßen Sanitizer verwenden
    # import bleach
    # return bleach.clean(input_html, tags=['p', 'b', 'i', 'em', 'strong'])

# SICHER: Groß-/kleinschreibungsunempfindliche Dateierweiterungsprüfung
def secure_file_upload(filename):
    """Dateierweiterung mit Groß-/Kleinschreibungsnormalisierung prüfen."""
    ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.pdf', '.txt'}

    # SICHER: Vor Prüfung in Kleinbuchstaben normalisieren
    normalized = filename.lower()

    # SICHER: Erweiterung korrekt extrahieren
    if '.' not in normalized:
        return False

    ext = '.' + normalized.rsplit('.', 1)[-1]

    # SICHER: Allowlist verwenden, nicht Denylist
    if ext not in ALLOWED_EXTENSIONS:
        return False

    # SICHER: Auch auf doppelte Erweiterungen prüfen
    if normalized.count('.') > 1:
        # Zusätzliche Prüfung für Dateien wie "image.jpg.php"
        parts = normalized.split('.')
        for part in parts[:-1]:
            if part in ['php', 'asp', 'jsp', 'exe', 'bat']:
                return False

    return True

# SICHER: Pfadprüfung mit Normalisierung
import os

def secure_path_check(path, base_dir):
    """Pfad mit ordnungsgemäßer Normalisierung prüfen."""
    # SICHER: Zuerst URL-dekodieren
    decoded = urllib.parse.unquote(path)

    # SICHER: Pfad normalisieren (löst .., ., mehrfache Schrägstriche auf)
    normalized = os.path.normpath(decoded)

    # SICHER: In absoluten Pfad auflösen
    full_path = os.path.abspath(os.path.join(base_dir, normalized))
    base_path = os.path.abspath(base_dir)

    # SICHER: Pfad ist unter Basisverzeichnis
    if not full_path.startswith(base_path + os.sep):
        return False

    return True

# SICHER: Unicode-Normalisierung für Benutzername
def secure_username_check(username):
    """Benutzername mit Unicode-Normalisierung prüfen."""
    # SICHER: Unicode normalisieren (NFKC normalisiert Ähnlichkeiten)
    normalized = unicodedata.normalize('NFKC', username)

    # SICHER: Groß-/kleinschreibungsunempfindlicher Vergleich
    normalized = normalized.lower()

    # SICHER: Gegen blockierte Benutzer prüfen
    blocked_users = {'admin', 'root', 'administrator', 'system'}

    if normalized in blocked_users:
        return False

    # SICHER: Zusätzliche Prüfungen für verwechselbare Zeichen
    # Nicht-ASCII-Zeichen entfernen, die Verwechslungen sein könnten
    ascii_only = normalized.encode('ascii', 'ignore').decode('ascii')
    if ascii_only != normalized:
        # Enthält Nicht-ASCII - zusätzliche Prüfung
        if ascii_only in blocked_users:
            return False  # "ɑdmin" normalisiert sich zu "admin"

    return True
// SICHER: JavaScript mit ordnungsgemäßer Äquivalenzbehandlung

// SICHER: Groß-/kleinschreibungsunempfindliche Hostname-Prüfung
function secureCheckRedirect(url) {
    try {
        const parsed = new URL(url);

        // SICHER: Hostname in Kleinbuchstaben normalisieren
        const hostname = parsed.hostname.toLowerCase();

        // SICHER: Allowlist statt Denylist verwenden
        const allowedHosts = ['example.com', 'trusted.com', 'api.example.com'];

        if (!allowedHosts.includes(hostname)) {
            return false;
        }

        // SICHER: Auch Protokoll prüfen
        if (parsed.protocol !== 'https:') {
            return false;
        }

        return true;
    } catch (e) {
        // Ungültige URL
        return false;
    }
}

// SICHER: URL-Validierung mit Normalisierung
function secureValidateUrl(url) {
    try {
        // SICHER: URL normalisieren
        const parsed = new URL(url);

        // SICHER: Auf Leerzeichen im Hostname prüfen (verdächtig)
        if (/\s/.test(parsed.hostname)) {
            return false;
        }

        // SICHER: Hostname normalisieren und prüfen
        const hostname = parsed.hostname.toLowerCase().trim();

        const allowedHosts = ['example.com', 'trusted.com'];

        // SICHER: Exakte Übereinstimmung oder Subdomain-Übereinstimmung
        for (const allowed of allowedHosts) {
            if (hostname === allowed ||
                hostname.endsWith('.' + allowed)) {
                return true;
            }
        }

        return false;
    } catch (e) {
        return false;
    }
}

// SICHER: Benutzername-Prüfung mit Normalisierung
function secureUsernameCheck(username) {
    // SICHER: Unicode mit String.normalize() normalisieren
    const normalized = username.normalize('NFKC').toLowerCase();

    const blockedUsers = new Set(['admin', 'root', 'administrator', 'system']);

    if (blockedUsers.has(normalized)) {
        return false;
    }

    // SICHER: Nur erlaubte Zeichen enthalten
    if (!/^[a-z0-9_-]+$/.test(normalized)) {
        // Enthält Zeichen außerhalb von Basis-ASCII
        // Könnten Unicode-Verwechslungen sein
        return false;
    }

    return true;
}
// SICHER: C mit ordnungsgemäßer Äquivalenzbehandlung

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

// SICHER: Groß-/kleinschreibungsunempfindliche Befehlsprüfung
bool secure_check_command(const char* cmd) {
    // SICHER: In Kleinbuchstaben für Vergleich konvertieren
    char* lower = strdup(cmd);
    if (lower == NULL) return false;

    for (char* p = lower; *p; p++) {
        *p = tolower(*p);
    }

    // SICHER: Allowlist-Ansatz verwenden
    const char* allowed_commands[] = {"list", "status", "help", "info"};
    bool allowed = false;

    for (size_t i = 0; i < sizeof(allowed_commands)/sizeof(allowed_commands[0]); i++) {
        if (strcmp(lower, allowed_commands[i]) == 0) {
            allowed = true;
            break;
        }
    }

    free(lower);
    return allowed;
}

// SICHER: Pfadprüfung mit Normalisierung
bool secure_check_path(const char* path, const char* base_dir,
                        char* result, size_t result_size) {
    // SICHER: Puffer für normalisierten Pfad allokieren
    char* normalized = realpath(path, NULL);
    if (normalized == NULL) {
        // Pfad existiert nicht oder ist ungültig
        return false;
    }

    // SICHER: Kanonisches Basisverzeichnis ermitteln
    char* base_canonical = realpath(base_dir, NULL);
    if (base_canonical == NULL) {
        free(normalized);
        return false;
    }

    // SICHER: Normalisierter Pfad beginnt mit Basisverzeichnis
    size_t base_len = strlen(base_canonical);
    bool safe = (strncmp(normalized, base_canonical, base_len) == 0) &&
                (normalized[base_len] == '/' || normalized[base_len] == '\0');

    if (safe && result != NULL) {
        strncpy(result, normalized, result_size - 1);
        result[result_size - 1] = '\0';
    }

    free(normalized);
    free(base_canonical);

    return safe;
}

// SICHER: Erweiterungsprüfung mit Groß-/Kleinschreibungsnormalisierung
bool secure_check_extension(const char* filename) {
    const char* ext = strrchr(filename, '.');
    if (ext == NULL) return false;

    // SICHER: Erweiterung in Kleinbuchstaben konvertieren
    char lower_ext[16];
    size_t i;
    for (i = 0; ext[i] && i < sizeof(lower_ext) - 1; i++) {
        lower_ext[i] = tolower(ext[i]);
    }
    lower_ext[i] = '\0';

    // SICHER: Allowlist sicherer Erweiterungen verwenden
    const char* safe_extensions[] = {".txt", ".pdf", ".jpg", ".jpeg", ".png", ".gif"};

    for (size_t j = 0; j < sizeof(safe_extensions)/sizeof(safe_extensions[0]); j++) {
        if (strcmp(lower_ext, safe_extensions[j]) == 0) {
            return true;
        }
    }

    return false;
}

// SICHER: Hilfsfunktion für groß-/kleinschreibungsunempfindlichen Zeichenkettenvergleich
int strcasecmp_safe(const char* s1, const char* s2) {
    while (*s1 && *s2) {
        int c1 = tolower((unsigned char)*s1);
        int c2 = tolower((unsigned char)*s2);
        if (c1 != c2) return c1 - c2;
        s1++;
        s2++;
    }
    return tolower((unsigned char)*s1) - tolower((unsigned char)*s2);
}

CVE-Beispiele

  • CVE-2021-39155: Hostname-Vergleich mit groß-/kleinschreibungssensitivem Abgleich umging Autorisierungsprüfungen.
  • CVE-2020-11053: HTML-kodierte Leerzeichen umgingen Weiterleitungs-URL-Validierung.
  • CVE-2005-0269: Datei-Upload-Filter prüfte nur Kleinbuchstaben-Erweiterungen, was Umgehung mit Großbuchstaben ermöglichte.
  • CVE-2001-1238: Prozessnamen mit Großbuchstaben könnten vom Filter nicht beendet werden.
  • CVE-2004-2214: Gemischt-geschriebene URIs umgingen Zugriffsbeschränkungen.

Verwandte CWEs

  • CWE-20: Unsachgemäße Eingabevalidierung (übergeordnet)
  • CWE-41: Unsachgemäße Auflösung von Pfadäquivalenz (verwandt)
  • CWE-178: Unsachgemäße Behandlung von Groß-/Kleinschreibung (verwandt)
  • CWE-1215: Datenvalidierungsprobleme (Kategorie)

Referenzen

  1. MITRE Corporation. "CWE-1289: Improper Validation of Unsafe Equivalence in Input." https://cwe.mitre.org/data/definitions/1289.html
  2. OWASP. "Input Validation Cheat Sheet"
  3. Unicode Consortium. "Unicode Security Considerations"