Mehrfache Interpretationen von UI-Eingaben

Beschreibung

Mehrfache Interpretationen von UI-Eingaben ist eine Schwachstelle, bei der die Benutzeroberfläche mehrere Interpretationen einer Benutzereingabe hat, aber den Benutzer nicht auffordert, wenn sie die weniger sichere Interpretation wählt. Wenn eine Anwendung Benutzereingaben akzeptiert, die auf mehrere Arten verstanden werden können, kann sie mit einer weniger sicheren Interpretation fortfahren, ohne den Benutzer auf diese Wahl hinzuweisen. Dies kann zu Sicherheitsschwachstellen führen, wenn sich die Systeminterpretation von der Absicht des Benutzers unterscheidet, was möglicherweise unbeabsichtigte Aktionen oder Sicherheitsumgehungen ermöglicht.

Risiko

Mehrfache Interpretationen von UI-Eingaben schaffen Sicherheitsrisiken, wenn das System stillschweigend eine weniger sichere Option wählt. Benutzer beabsichtigen möglicherweise restriktive Einstellungen, aber das System interpretiert ihre Eingabe freizügig. Unterschiedlich interpretierte Datumsformate können während falscher Zeiträume Zugriff gewähren. Dateipfadinterpretationen können Zugriff auf unbeabsichtigte Verzeichnisse ermöglichen. Mehrdeutigkeiten bei der Zeichenkodierung können die Eingabevalidierung umgehen. Das Risiko wird verstärkt, weil Benutzer nicht informiert werden, wenn die weniger sichere Interpretation gewählt wird, was zu falschem Vertrauen in die Sicherheit ihrer Konfiguration führt.

Lösung

Verwenden Sie einen "bekannte gute Eingaben akzeptieren"-Ansatz zur Eingabevalidierung mit Allowlists akzeptabler Eingaben, die strikt den Spezifikationen entsprechen. Lehnen Sie jede Eingabe ab, die strenge Validierungskriterien nicht erfüllt oder mehrere Interpretationen haben könnte. Wenn mehrere gültige Interpretationen existieren, fordern Sie den Benutzer auf, seine Absicht zu klären, anstatt stillschweigend eine zu wählen. Berücksichtigen Sie Länge, Typ, akzeptable Wertebereiche, fehlende oder zusätzliche Eingaben, Syntax, Konsistenz über Felder hinweg und Geschäftslogik-Konformität. Dekodieren und kanonisieren Sie alle Eingaben zur internen Darstellung der Anwendung vor der Validierung. Vermeiden Sie doppelte Dekodierung, um Bypass-Angriffe zu verhindern.

Häufige Auswirkungen

AuswirkungDetails
SonstigesUmfang: Sonstiges

Variiert je nach Kontext - Die Auswirkung hängt von der gewählten spezifischen Interpretation und den Sicherheitsauswirkungen dieser Wahl im Vergleich zur beabsichtigten Interpretation des Benutzers ab.

Beispielcode

Anfälliger Code

# Anfällig: Datumseingabe mit mehreren Interpretationen
class VulnerableDateParser:
    def parse_access_expiry(self, date_string):
        # Anfällig: Mehrere Datumsformate ohne Klärung akzeptiert
        # "01/02/2024" - Ist das der 2. Januar oder der 1. Februar?

        # Wählt stillschweigend MM/TT/JJJJ (amerikanisches) Format
        parts = date_string.split('/')
        month = int(parts[0])
        day = int(parts[1])
        year = int(parts[2])

        # Benutzer in Europa beabsichtigte TT/MM/JJJJ
        # Er setzte Ablauf auf 1. Feb (01/02/2024)
        # System interpretiert als 2. Jan - Zugriff läuft einen Monat zu früh ab
        # Oder schlimmer: Benutzer setzt Ablauf auf 2. Jan, System interpretiert als 1. Feb
        # Zugriff besteht einen Monat langer

        return datetime(year, month, day)

    def parse_time_restriction(self, time_input):
        # Anfällig: "9:00" - vormittags oder abends?
        # System verwendet standardmäßig 24-Stunden-Interpretation
        # "9:00" wird zu 09:00 (morgens)

        # Benutzer beabsichtigte 21 Uhr Einschränkung
        # System erlaubt Zugriff um 9 Uhr stattdessen
        hours, minutes = map(int, time_input.split(':'))
        return time(hours, minutes)
// Anfällig: Berechtigungseingabe mit mehrdeutiger Interpretation
class VulnerablePermissionParser {

    parsePermissionLevel(input) {
        // Anfällig: "admin" könnte verschiedene Dinge bedeuten
        // - Voller Administrator
        // - Administrator eines bestimmten Bereichs
        // - Nur-Lesen-Admin-Zugriff

        const levels = {
            'admin': 'FULL_ADMIN',    // System nimmt das Freizügigste an
            'user': 'STANDARD_USER',
            'guest': 'GUEST'
        };

        // Benutzer tippt "admin" und meint Bereichsadmin
        // System gewährt volle Administratorrechte
        return levels[input.toLowerCase()] || 'GUEST';
    }

    parseIPRange(input) {
        // Anfällig: "192.168.1.0" - Ist das eine einzelne IP oder ein /24 Netzwerk?
        // System interpretiert stillschweigend als /24 Netzwerk

        if (!input.includes('/')) {
            // Anfällig: Nimmt Netzwerk an ohne Bestätigung
            if (input.endsWith('.0')) {
                return input + '/24';  // Fügt stillschweigend Subnetz hinzu
            }
        }

        // Benutzer beabsichtigte einzelne IP 192.168.1.0
        // System erlaubt gesamten 192.168.1.0/24 Bereich (256 IPs)
        return input;
    }
}
// Anfällig: Dateipfad mit mehreren Interpretationen
public class VulnerablePathParser {

    public File resolvePath(String userPath) {
        // Anfällig: Relativ vs. absolute Interpretation
        // "config/settings.json" - relativ zu was?

        // System verwendet stillschweigend aktuelles Arbeitsverzeichnis
        // Benutzer könnte Anwendungswurzel gemeint haben

        File file = new File(userPath);

        // Anfällig: ".." Interpretation variiert
        // Auf manchen Systemen greift "../secret" auf Elternverzeichnis zu
        // Benutzer realisiert möglicherweise nicht, dass Pfadtraversierung möglich ist

        return file.getCanonicalFile();
    }

    public String parseFileType(String filename) {
        // Anfällig: "document.txt.exe" - was ist der Typ?
        // System verwendet letzte Erweiterung (.exe)
        // Benutzer sieht .txt und denkt es ist sicher

        int lastDot = filename.lastIndexOf('.');
        if (lastDot > 0) {
            return filename.substring(lastDot + 1);
        }
        return "";

        // Keine Warnung, dass Datei tatsächlich ausführbar ist
    }
}
// Anfällig: Numerische Eingabe mit mehreren Interpretationen
#include <stdlib.h>
#include <string.h>

int parse_port_number(const char *input) {
    // Anfällig: "010" - dezimal 10 oder oktal 8?
    // strtol mit Basis 0 interpretiert führende Null als oktal

    long port = strtol(input, NULL, 0);

    // Benutzer tippt "010" und meint Port 10
    // System interpretiert als oktal 8
    // Falscher Port wird geöffnet

    return (int)port;
}

int parse_permission_mask(const char *input) {
    // Anfällig: "755" - String oder oktal?
    // Benutzer tippt 755 und meint rwxr-xr-x

    int mask;

    // Anfällig: Verschiedene Interpretation je nach Kontext
    if (input[0] == '0') {
        // Behandelt als oktal
        mask = strtol(input, NULL, 8);
    } else {
        // Behandelt als dezimal - falsch!
        mask = strtol(input, NULL, 10);  // 755 dezimal != 0755 oktal
    }

    // "755" wird zu 755 dezimal = 01363 oktal
    // Völlig falsche Berechtigungen gesetzt
    return mask;
}

Korrigierter Code

# Korrigiert: Datumseingabe mit expliziter Formatauswahl
from datetime import datetime, time

class SecureDateParser:
    SUPPORTED_FORMATS = {
        'ISO': '%Y-%m-%d',
        'US': '%m/%d/%Y',
        'EU': '%d/%m/%Y',
        'UK': '%d-%m-%Y'
    }

    def parse_access_expiry(self, date_string, format_hint=None):
        # Korrigiert: Erfordert explizites Format oder erkennt und bestätigt
        if format_hint and format_hint in self.SUPPORTED_FORMATS:
            return self._parse_with_format(date_string, self.SUPPORTED_FORMATS[format_hint])

        # Korrigiert: Erkennt mögliche Formate
        possible_formats = self._detect_possible_formats(date_string)

        if len(possible_formats) > 1:
            # Korrigiert: Wirft Fehler, der Klärung erfordert
            raise AmbiguousInputError(
                f"Datum '{date_string}' hat mehrere Interpretationen: {possible_formats}. "
                "Bitte Format explizit angeben."
            )

        return self._parse_with_format(date_string, possible_formats[0])

    def _detect_possible_formats(self, date_string):
        possible = []
        for name, fmt in self.SUPPORTED_FORMATS.items():
            try:
                datetime.strptime(date_string, fmt)
                possible.append((name, fmt))
            except ValueError:
                pass
        return possible

    def parse_time_restriction(self, time_input):
        # Korrigiert: Erfordert explizites AM/PM oder 24-Stunden-Format
        if 'AM' in time_input.upper() or 'PM' in time_input.upper():
            # 12-Stunden-Format mit explizitem Indikator
            return datetime.strptime(time_input.upper(), '%I:%M %p').time()
        elif len(time_input.split(':')[0]) == 2:
            # 24-Stunden-Format (muss führende Null haben: "09:00")
            return datetime.strptime(time_input, '%H:%M').time()
        else:
            raise AmbiguousInputError(
                f"Zeit '{time_input}' ist mehrdeutig. Verwenden Sie '09:00' für 24-Stunden "
                "oder '9:00 AM/PM' für 12-Stunden-Format."
            )
// Korrigiert: Berechtigungseingabe mit expliziter Klärung
class SecurePermissionParser {

    parsePermissionLevel(input) {
        // Korrigiert: Erfordert spezifische, eindeutige Berechtigungsnamen
        const levels = {
            'full_administrator': 'FULL_ADMIN',
            'section_administrator': 'SECTION_ADMIN',
            'read_only_admin': 'READONLY_ADMIN',
            'standard_user': 'STANDARD_USER',
            'guest': 'GUEST'
        };

        const normalized = input.toLowerCase().replace(/\s+/g, '_');

        if (!levels[normalized]) {
            throw new AmbiguousInputError(
                `Unbekannte Berechtigungsstufe: '${input}'. ` +
                `Gültige Optionen: ${Object.keys(levels).join(', ')}`
            );
        }

        return levels[normalized];
    }

    parseIPRange(input) {
        // Korrigiert: Erfordert explizite CIDR-Notation
        if (!input.includes('/')) {
            // Korrigiert: Fordert Klärung an
            throw new AmbiguousInputError(
                `IP '${input}' ist mehrdeutig. ` +
                `Verwenden Sie '${input}/32' für einzelne IP oder '${input}/24' für Netzwerk.`
            );
        }

        // Korrigiert: Validiert CIDR-Notation
        const [ip, mask] = input.split('/');
        const maskNum = parseInt(mask);

        if (isNaN(maskNum) || maskNum < 0 || maskNum > 32) {
            throw new InvalidInputError(`Ungültige CIDR-Maske: ${mask}`);
        }

        return input;
    }
}
// Korrigiert: Dateipfad mit expliziter Interpretation
public class SecurePathParser {

    private final Path applicationRoot;

    public SecurePathParser(Path applicationRoot) {
        this.applicationRoot = applicationRoot.toAbsolutePath();
    }

    public Path resolvePath(String userPath, PathContext context) throws AmbiguousInputException {
        // Korrigiert: Erfordert expliziten Kontext für Pfadauflösung
        Path resolved;

        switch (context) {
            case ABSOLUTE:
                resolved = Paths.get(userPath).toAbsolutePath();
                break;

            case RELATIVE_TO_APP_ROOT:
                resolved = applicationRoot.resolve(userPath).normalize();
                break;

            case RELATIVE_TO_WORKING_DIR:
                resolved = Paths.get(userPath).toAbsolutePath().normalize();
                break;

            default:
                throw new AmbiguousInputException(
                    "Pfadkontext muss explizit angegeben werden"
                );
        }

        // Korrigiert: Prüft auf Pfadtraversierung
        if (context == PathContext.RELATIVE_TO_APP_ROOT) {
            if (!resolved.startsWith(applicationRoot)) {
                throw new SecurityException("Pfadtraversierung erkannt");
            }
        }

        return resolved;
    }

    public FileTypeResult parseFileType(String filename) {
        // Korrigiert: Erkennt und warnt vor mehrfachen Erweiterungen
        String[] parts = filename.split("\\.");

        if (parts.length > 2) {
            // Korrigiert: Mehrere Erweiterungen erkannt
            String visibleExt = parts[parts.length - 2];
            String actualExt = parts[parts.length - 1];

            if (isDangerousExtension(actualExt) && !isDangerousExtension(visibleExt)) {
                throw new AmbiguousInputException(
                    String.format(
                        "Datei '%s' hat mehrere Erweiterungen. " +
                        "Erscheint als '.%s' aber ist tatsächlich '.%s' (potenziell gefährlich)",
                        filename, visibleExt, actualExt
                    )
                );
            }
        }

        return new FileTypeResult(parts[parts.length - 1], parts.length > 2);
    }

    private boolean isDangerousExtension(String ext) {
        return Set.of("exe", "bat", "cmd", "ps1", "vbs", "js", "msi")
                  .contains(ext.toLowerCase());
    }
}
// Korrigiert: Numerische Eingabe mit expliziter Basisangabe
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

typedef struct {
    int value;
    int base_used;
    char error[256];
} ParseResult;

ParseResult parse_port_number(const char *input, int explicit_base) {
    ParseResult result = {0, 10, ""};
    char *endptr;

    // Korrigiert: Erfordert explizite Basis oder lehnt mehrdeutige Eingabe ab
    if (explicit_base == 0) {
        // Korrigiert: Prüft auf mehrdeutige führende Null
        if (input[0] == '0' && input[1] != '\0' && input[1] != 'x') {
            snprintf(result.error, sizeof(result.error),
                     "Mehrdeutige Eingabe '%s'. Verwenden Sie '0o' Präfix für oktal, "
                     "oder entfernen Sie führende Null für dezimal.", input);
            return result;
        }
    }

    errno = 0;
    long port = strtol(input, &endptr, explicit_base);

    if (errno != 0 || *endptr != '\0') {
        snprintf(result.error, sizeof(result.error),
                 "Ungültige Portnummer: %s", input);
        return result;
    }

    if (port < 1 || port > 65535) {
        snprintf(result.error, sizeof(result.error),
                 "Port muss zwischen 1 und 65535 liegen");
        return result;
    }

    result.value = (int)port;
    result.base_used = explicit_base ? explicit_base : 10;
    return result;
}

ParseResult parse_permission_mask(const char *input) {
    ParseResult result = {0, 8, ""};
    char *endptr;

    // Korrigiert: Erfordert immer oktales Präfix für Berechtigungsmasken
    if (input[0] != '0' || (input[1] != '\0' && input[1] != 'o' &&
                           !('0' <= input[1] && input[1] <= '7'))) {
        snprintf(result.error, sizeof(result.error),
                 "Berechtigungsmaske muss im Oktalformat sein (z.B. '0755' oder '0o755')");
        return result;
    }

    // Korrigiert: Parst als explizites Oktal
    const char *parse_start = input;
    if (input[0] == '0' && input[1] == 'o') {
        parse_start = input + 2;  // Überspringe "0o" Präfix
    }

    errno = 0;
    long mask = strtol(parse_start, &endptr, 8);

    if (errno != 0 || *endptr != '\0' || mask < 0 || mask > 0777) {
        snprintf(result.error, sizeof(result.error),
                 "Ungültige Berechtigungsmaske: %s", input);
        return result;
    }

    result.value = (int)mask;
    return result;
}

CVE-Beispiele

Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE aufgeführt. Das Muster ist jedoch häufig bei:

  • Datum/Zeit-Parsing-Schwachstellen, die zu Zugriffskontrollumgehungen führen
  • Dateityperkennung-Umgehungen durch mehrdeutige Erweiterungen
  • Numerische Interpretationsprobleme, die Konfigurationsfehler verursachen

Referenzen

  1. MITRE Corporation. "CWE-450: Multiple Interpretations of UI Input." https://cwe.mitre.org/data/definitions/450.html
  2. OWASP. "Input Validation Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html