Unvollständiges Modell der Endpunkt-Funktionen

Beschreibung

Unvollständiges Modell der Endpunkt-Funktionen ist eine Schwachstelle, bei der ein Produkt als Vermittler oder Monitor zwischen zwei oder mehr Endpunkten agiert, aber kein vollständiges Modell der Funktionen, Verhaltensweisen oder des Zustands eines Endpunkts hat. Dieses unvollständige Verständnis veranlasst das Produkt, falsche Aktionen basierend auf fehlerhaften Annahmen darüber durchzuführen, wie Endpunkte Daten interpretieren oder verarbeiten werden. Die Schwäche betrifft häufig sicherheitskritische Vermittler wie Firewalls, Proxies, Intrusion-Detection-Systeme und Antivirus-Gateways, die verschiedene Endpunkt-Verhaltensweisen verstehen müssen, um korrekt zu funktionieren.

Risiko

Unvollständige Endpunkt-Modellierung erzeugt schwerwiegende Sicherheitslücken in Vermittlersystemen. Angreifer nutzen Interpretationsunterschiede zwischen dem Vermittler und den tatsächlichen Endpunkten aus, um Sicherheitskontrollen zu umgehen, bösartigen Inhalt einzuschleusen oder Erkennung zu umgehen. HTTP-Request-Smuggling-Angriffe nutzen Unterschiede darin, wie Proxies und Backend-Server HTTP-Header parsen. E-Mail-Gateway-Umgehungen treten auf, wenn Antivirus-Produkte falsch einschätzen, wie Client-Anwendungen Anhänge behandeln. Web-Application-Firewalls versagen, wenn sie Browser-Quirks nicht modellieren, die Angreifer ausnutzen. Diese Schwachstellen sind besonders gefährlich, weil sie Sicherheitsinfrastruktur untergraben, auf die Organisationen zur Verteidigung angewiesen sind.

Lösung

Erstellen Sie umfassende Modelle von Endpunkt-Verhaltensweisen einschließlich Randfälle und versionsspezifische Eigenheiten. Implementieren Sie striktes Parsing, das mehrdeutige oder fehlerhafte Daten ablehnt, anstatt Absichten zu erraten. Verwenden Sie Kanonisierung, um Daten vor der Verarbeitung zu normalisieren. Wenn mehrere Interpretationen möglich sind, wählen Sie die restriktivste Interpretation oder lehnen Sie die Daten vollständig ab. Halten Sie Endpunkt-Verhaltensmodelle aktuell, wenn neue Versionen Änderungen einführen. Erwägen Sie Defense-in-Depth, bei der mehrere unabhängige Sicherheitsschichten Endpunkte unterschiedlich modellieren. Testen Sie das Vermittlerverhalten gegen tatsächliche Endpunkt-Implementierungen, um Annahmen zu verifizieren.

Häufige Auswirkungen

AuswirkungDetails
IntegritätUmfang: Integrität

Unerwarteter Zustand - Vermittler trifft falsche Entscheidungen basierend auf fehlerhaftem Verständnis des Endpunkt-Verhaltens, wodurch bösartiger Inhalt oder Anfragen durch Sicherheitskontrollen passieren können.
ZugriffskontrolleUmfang: Zugriffskontrolle

Schutzmechanismus umgehen - Angreifer können Sicherheitsvermittler umgehen, indem sie Unterschiede darin ausnutzen, wie der Vermittler und der Endpunkt Daten interpretieren.

Beispielcode

Anfälliger Code

# Anfällig: HTTP-Proxy mit unvollständigem Request-Parsing-Modell
class VulnerableHTTPProxy:
    def parse_request(self, raw_request):
        # Anfällig: Behandelt nur Standard-Content-Length
        headers = {}
        lines = raw_request.split('\r\n')

        for line in lines[1:]:
            if ': ' in line:
                key, value = line.split(': ', 1)
                headers[key] = value

        # Anfällig: Behandelt Transfer-Encoding nicht
        # Backend-Server kann Chunked-Encoding verwenden während Proxy Content-Length nutzt
        content_length = int(headers.get('Content-Length', 0))

        return {
            'method': lines[0].split()[0],
            'path': lines[0].split()[1],
            'headers': headers,
            'body_length': content_length
        }

    def forward_request(self, request, raw_data):
        # Anfällig: Request-Smuggling möglich
        # Proxy sieht eine Anfrage, Backend sieht zwei
        # Angreifer sendet:
        # POST / HTTP/1.1
        # Content-Length: 4
        # Transfer-Encoding: chunked
        #
        # 0
        #
        # GET /admin HTTP/1.1

        # Proxy verwendet Content-Length (4 Bytes), denkt Body ist "0\r\n\r"
        # Backend verwendet Transfer-Encoding chunked, sieht Ende der ersten Anfrage
        # Verarbeitet dann "GET /admin" als zweite Anfrage

        backend_response = self.backend.send(raw_data)
        return backend_response
// Anfällig: E-Mail-Gateway mit unvollständigem Anhang-Modell
public class VulnerableMailGateway {

    public boolean scanAttachment(MimeBodyPart attachment) throws Exception {
        String filename = attachment.getFileName();
        String contentType = attachment.getContentType();

        // Anfällig: Prüft nur deklarierten Content-Type
        // Modelliert nicht, wie verschiedene E-Mail-Clients Anhänge behandeln
        if (contentType.startsWith("text/plain")) {
            // Nimmt an, Klartext ist sicher
            return true;
        }

        // Anfällig: Erweiterungsbasierte Prüfung
        // Verschiedene Clients können basierend auf unterschiedlichen Kriterien ausführen
        if (filename.endsWith(".txt")) {
            return true;  // Als sicher betrachtet
        }

        // Anfällig: Berücksichtigt nicht:
        // - Outlook führt .txt.exe aus (versteckte Erweiterungen)
        // - Clients verwenden MIME-Typ zum Überschreiben der Erweiterung
        // - Zeichencodierungstricks (RTL-Override)
        // - Zone-Identifier-Verhalten unter Windows

        return scanWithAntivirus(attachment);
    }

    // Anfällig: Unvollständiges Modell der Inhaltsprüfung
    public boolean inspectContent(InputStream content) {
        // Scannt nur erstes 1MB
        // Endpunkt kann gesamte Datei verarbeiten
        byte[] sample = new byte[1024 * 1024];
        int read = content.read(sample);

        return !containsMaliciousSignature(sample);
    }
}
// Anfällig: Firewall mit unvollständigem Protokoll-Modell
#include <stdio.h>
#include <string.h>

typedef struct {
    char *data;
    size_t length;
} HttpRequest;

// Anfällig: Unvollständiges HTTP-Parsing
int firewall_inspect_http(const char *packet, size_t len) {
    // Anfällig: Sucht nur nach Standardmustern
    // Modelliert nicht HTTP/0.9, HTTP/2 oder Randfälle

    if (strncmp(packet, "GET ", 4) == 0 ||
        strncmp(packet, "POST ", 5) == 0) {

        // Anfällig: Einfache URL-Extraktion
        char *path_start = strchr(packet, ' ') + 1;
        char *path_end = strchr(path_start, ' ');

        size_t path_len = path_end - path_start;
        char path[1024];
        strncpy(path, path_start, path_len);
        path[path_len] = '\0';

        // Anfällig: Behandelt URL-Encoding-Varianten nicht
        // Backend kann %2e%2e als ".." decodieren während Firewall es nicht tut
        if (strstr(path, "../") != NULL) {
            return BLOCK;  // Path-Traversal
        }

        // Anfällig: Modelliert nicht das URL-Parsing des Backends
        // /admin;bypass.jpg könnte blockiert werden
        // Aber /admin%3bbypass.jpg passiert Firewall, wird vom Backend decodiert
        if (strstr(path, "/admin") != NULL) {
            return BLOCK;
        }
    }

    return ALLOW;
}

Korrigierter Code

# Korrigiert: HTTP-Proxy mit umfassendem Request-Parsing
class SecureHTTPProxy:
    def parse_request(self, raw_request):
        headers = {}
        lines = raw_request.split('\r\n')

        for line in lines[1:]:
            if ': ' in line:
                key, value = line.split(': ', 1)
                # Korrigiert: Header-Namen normalisieren
                key = key.lower().strip()
                headers[key] = value.strip()

        # Korrigiert: Mehrdeutige Anfragen erkennen
        has_content_length = 'content-length' in headers
        has_transfer_encoding = 'transfer-encoding' in headers

        # Korrigiert: Mehrdeutige Anfragen gemäß RFC 7230 ablehnen
        if has_content_length and has_transfer_encoding:
            raise AmbiguousRequestError(
                "Anfrage enthält sowohl Content-Length als auch Transfer-Encoding"
            )

        # Korrigiert: Smuggling-Versuche erkennen
        if has_transfer_encoding:
            te_value = headers['transfer-encoding'].lower()
            # Korrigiert: Fehlerhaftes Transfer-Encoding ablehnen
            if te_value != 'chunked':
                raise InvalidRequestError(
                    f"Ungültiges Transfer-Encoding: {te_value}"
                )

        return self._normalize_request(lines, headers)

    def forward_request(self, request, raw_data):
        # Korrigiert: Anfrage re-serialisieren um Konsistenz zu gewährleisten
        normalized = self._serialize_request(request)

        # Korrigiert: Auch striktes Parsing für Antwort verwenden
        backend_response = self.backend.send(normalized)

        # Korrigiert: Antwort-Integrität validieren
        self._validate_response(backend_response)

        return backend_response

    def _serialize_request(self, request):
        # Korrigiert: Eindeutige Anfrage erstellen
        # Nur Content-Length für Anfragen mit Body verwenden
        lines = [f"{request['method']} {request['path']} HTTP/1.1"]

        for key, value in request['headers'].items():
            if key.lower() not in ['transfer-encoding', 'content-length']:
                lines.append(f"{key}: {value}")

        if request.get('body'):
            lines.append(f"Content-Length: {len(request['body'])}")

        return '\r\n'.join(lines) + '\r\n\r\n' + request.get('body', '')
// Korrigiert: E-Mail-Gateway mit umfassendem Anhang-Modell
public class SecureMailGateway {

    private static final Map<String, Set<String>> SAFE_TYPES = Map.of(
        "text/plain", Set.of("txt", "csv"),
        "image/jpeg", Set.of("jpg", "jpeg"),
        "image/png", Set.of("png")
    );

    public boolean scanAttachment(MimeBodyPart attachment) throws Exception {
        String filename = attachment.getFileName();
        String declaredType = attachment.getContentType();

        // Korrigiert: Gefährliche Dateinamen-Tricks erkennen
        if (containsRtlOverride(filename) || hasHiddenExtension(filename)) {
            log.warn("Verdächtiger Dateiname erkannt: {}", filename);
            return false;
        }

        // Korrigiert: Tatsächlichen Content-Type mit Deklaration verifizieren
        byte[] content = readAllBytes(attachment.getInputStream());
        String actualType = detectContentType(content);

        // Korrigiert: Content-Type muss deklariertem Typ entsprechen
        if (!typesMatch(declaredType, actualType)) {
            log.warn("Content-Type-Mismatch: deklariert={}, tatsächlich={}",
                     declaredType, actualType);
            return false;
        }

        // Korrigiert: Erweiterung muss Content-Type entsprechen
        String extension = getExtension(filename);
        if (!extensionMatchesType(extension, actualType)) {
            log.warn("Erweiterung/Typ-Mismatch: ext={}, typ={}",
                     extension, actualType);
            return false;
        }

        // Korrigiert: Gesamten Inhalt scannen, nicht nur Stichprobe
        return scanWithAntivirus(content);
    }

    private boolean containsRtlOverride(String filename) {
        // Korrigiert: Unicode-Tricks erkennen
        return filename.contains("\u202E") ||  // RTL-Override
               filename.contains("\u200B") ||  // Null-Breite-Leerzeichen
               filename.contains("\u00A0");    // Geschütztes Leerzeichen
    }

    private boolean hasHiddenExtension(String filename) {
        // Korrigiert: Doppelte Erweiterungen erkennen, die wahren Typ verbergen
        String[] dangerousExts = {".exe", ".bat", ".cmd", ".ps1", ".vbs", ".js"};
        String lowerName = filename.toLowerCase();

        for (String ext : dangerousExts) {
            if (lowerName.contains(ext + ".")) {
                return true;  // z.B. "file.exe.txt"
            }
        }
        return false;
    }
}
// Korrigiert: Firewall mit umfassendem Protokoll-Modell
#include <stdio.h>
#include <string.h>
#include <ctype.h>

// Korrigiert: Umfassende URL-Decodierung
char *url_decode(const char *src, size_t len) {
    char *decoded = malloc(len + 1);
    char *dst = decoded;

    for (size_t i = 0; i < len; i++) {
        if (src[i] == '%' && i + 2 < len &&
            isxdigit(src[i+1]) && isxdigit(src[i+2])) {
            // Korrigiert: Prozent-codierte Zeichen decodieren
            char hex[3] = {src[i+1], src[i+2], '\0'};
            *dst++ = (char)strtol(hex, NULL, 16);
            i += 2;
        } else if (src[i] == '+') {
            *dst++ = ' ';
        } else {
            *dst++ = src[i];
        }
    }
    *dst = '\0';

    return decoded;
}

// Korrigiert: Pfad für konsistenten Vergleich normalisieren
char *normalize_path(const char *path) {
    // Korrigiert: URL-Encoding zuerst decodieren
    char *decoded = url_decode(path, strlen(path));

    // Korrigiert: Path-Traversal-Sequenzen auflösen
    char *normalized = resolve_path_traversal(decoded);

    // Korrigiert: Kleinschreibung für case-insensitiven Vergleich
    for (char *p = normalized; *p; p++) {
        *p = tolower(*p);
    }

    free(decoded);
    return normalized;
}

int firewall_inspect_http(const char *packet, size_t len) {
    HttpRequest req;

    // Korrigiert: Umfassenden HTTP-Parser verwenden
    int parse_result = parse_http_strict(packet, len, &req);
    if (parse_result != PARSE_OK) {
        // Korrigiert: Fehlerhafte Anfragen ablehnen
        return BLOCK;
    }

    // Korrigiert: Pfad vor Prüfung normalisieren
    char *normalized_path = normalize_path(req.path);

    // Korrigiert: Gegen normalisierten Pfad prüfen
    if (contains_path_traversal(normalized_path)) {
        free(normalized_path);
        return BLOCK;
    }

    // Korrigiert: Normalisierten Vergleich verwenden
    if (strstr(normalized_path, "/admin") != NULL) {
        free(normalized_path);
        return BLOCK;
    }

    // Korrigiert: Auch auf Bypass-Techniken prüfen
    // Null-Bytes, Semikolons etc. entfernen, die Backends interpretieren könnten
    if (contains_suspicious_chars(req.path)) {
        free(normalized_path);
        return BLOCK;
    }

    free(normalized_path);
    return ALLOW;
}

CVE-Beispiele

  • CVE-2005-1992 - HTTP-Request-Smuggling durch Unterschiede darin, wie Proxy und Backend-Server Transfer-Encoding-Header interpretieren.
  • CVE-2020-8287 - Node.js HTTP-Parser erlaubte Header-Smuggling aufgrund unvollständiger Modellierung der HTTP-Spezifikation.
  • CVE-2021-21295 - Netty HTTP-Request-Smuggling durch unterschiedliche Interpretationen von Content-Length.

Referenzen

  1. MITRE Corporation. "CWE-437: Incomplete Model of Endpoint Features." https://cwe.mitre.org/data/definitions/437.html
  2. PortSwigger. "HTTP Request Smuggling." https://portswigger.net/web-security/request-smuggling