Fehlerhafte Analyse von Zahlen mit verschiedenen Basen

Beschreibung

Fehlerhafte Analyse von Zahlen mit verschiedenen Basen tritt auf, wenn eine Anwendung dezimale Zahlenwerte annimmt, aber Eingaben mit alternativen Basen nicht korrekt verarbeitet. Zahlen mit dem Prafix "0" werden oft als oktal behandelt, während "0x" hexadezimal anzeigt, was zu unerwartetem Verhalten führen kann. Wenn Entwickler ausschließlich dezimale Eingaben annehmen, könnte der Code falsche Zahlen erzeugen, wenn die Eingaben mit einer anderen Basis analysiert werden. Ein bemerkenswertes Beispiel betrifft IP-Adressen: "0127.0.0.1" in oktal entspricht 87.0.0.1 in dezimal. Diese Schwachstelle ermöglicht es Angreifern, Zugangskontrollen zu umgehen, SSRF-Schutzmassnahmen zu manipulieren und symbolische Bezeichner auszunutzen, die als Zahlen behandelt werden.

Risiko

Radix-Parsing-Schwachstellen haben schwerwiegende Auswirkungen. Umgehung von Zugangskontrollen durch IP-Adress-Manipulation. Umgehung von SSRF-Filtern durch oktale/hexadezimale Adressen. Fehlerhafte Berechtigungsprüfungen. Unerwartetes Verhalten bei numerischen Vergleichen. Konfigurationsfehler durch Tippfehler. Umgehung von Sicherheitsgrenzen. Probleme mit der Datenintegritat. Hohe Wahrscheinlichkeit bei der Validierung von IP-Adressen oder anderen numerischen Bezeichnern.

Lösung

Numerische Zeichenketten vor bedingten Prüfungen in Basis-10-Ganzzahlen konvertieren, um oktale/hexadezimale Verarbeitung während der Implementierungsphase zu verhindern. Auf Basis-Indikatoren (wie "0x") prüfen und Zeichenketten in geeignete Basen konvertieren, während nicht unterstützte Alternativen abgelehnt werden. Verankerte reguläre Ausdrucke (^ und $) bei der Validierung von IP-Adressen verwenden, um zu verhindern, dass Basis-vorangestellte Adressen übereinstimmen. Die Basis beim Parsen von Zahlen explizit angeben.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Angreifer können mit unerwarteten Zahlenbasen auf private Ressourcen zugreifen.
IntegritatBereich: Integritat

Angreifer können eine unerwartete Zahlenbasis verwenden, um Zugangskontrollmechanismen zu umgehen oder zu manipulieren.

Beispielcode

Verwundbarer Code

# VERWUNDBAR: Python IP-Validierung mit Radix-Problemen

import subprocess
import re

# VERWUNDBAR: Oktale Analyse bei IP-Validierung
def vulnerable_validate_ip(ip_string):
    """Validiert IP, gibt aber Originalzeichenkette mit potenziellem Oktalwert zurück."""
    parts = ip_string.split('.')

    if len(parts) != 4:
        return None

    for part in parts:
        try:
            # VERWUNDBAR: int() verarbeitet oktal (0-Prafix) und hex (0x-Prafix)
            value = int(part, 0)  # Basis 0 = automatische Erkennung

            # Selbst mit expliziter Basis 10 könnten führende Nullen entfernt werden
            # aber die Originalzeichenkette wird zurückgegeben
            if value < 0 or value > 255:
                return None
        except ValueError:
            return None

    # VERWUNDBAR: Gibt Originalzeichenkette zurück, nicht normalisiertes Dezimal
    return ip_string

def vulnerable_ping(user_ip):
    """Pingt benutzerbereitgestellte IP-Adresse."""
    validated_ip = vulnerable_validate_ip(user_ip)

    if validated_ip:
        # VERWUNDBAR: System interpretiert oktal anders
        # Benutzereingabe: "0127.0.0.1"
        # Validierung sieht: 127.0.0.1 (nach int-Konvertierung)
        # ping sieht: 87.0.0.1 (oktale Interpretation)
        subprocess.run(['ping', '-c', '1', validated_ip])

# Angriff: vulnerable_ping("0127.0.0.1")
# Validiert als 127.0.0.1, pingt aber 87.0.0.1


# VERWUNDBAR: Regex ohne Anker
def vulnerable_validate_ip_regex(ip_string):
    """IP-Validierung mit nicht-verankertem Regex."""
    # VERWUNDBAR: Keine ^ und $ Anker
    pattern = r'(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})'

    match = re.search(pattern, ip_string)
    if match:
        # VERWUNDBAR: Prafix vor IP-Adresse wird ignoriert
        # "0x63.63.63.63" stimmt mit "63.63.63.63" überein
        octets = [int(g) for g in match.groups()]
        if all(0 <= o <= 255 for o in octets):
            return ip_string  # Gibt Original mit Prafix zurück

    return None

def vulnerable_fetch(user_url):
    """Ruft URL mit IP-Validierung ab."""
    # IP aus URL extrahieren
    ip_match = re.search(r'//([^/]+)', user_url)
    if ip_match:
        ip = ip_match.group(1)

        # VERWUNDBAR: Validierung verhindert kein Hex-Prafix
        if vulnerable_validate_ip_regex(ip):
            # "0x7f.0.0.1" besteht die Validierung
            # System könnte als 127.0.0.1 (localhost) interpretieren
            # SSRF zu internen Diensten
            requests.get(user_url)

# Angriff: vulnerable_fetch("http://0x7f.0.0.1/admin")
// VERWUNDBAR: JavaScript Zahlenanalyse-Probleme

// VERWUNDBAR: parseInt ohne explizite Basis
function vulnerableParsePort(portString) {
    // VERWUNDBAR: Fuhrende 0 verursacht oktale Interpretation in einigen Kontexten
    const port = parseInt(portString);  // Fehlender Radix-Parameter

    // "010" könnte als 8 (oktal) oder 10 (dezimal) analysiert werden
    // abhängig von JavaScript-Engine und Strict Mode

    if (port > 0 && port < 65536) {
        return port;
    }
    return null;
}

// VERWUNDBAR: Number() mit verschiedenen Basen
function vulnerableValidateOctet(octetString) {
    // VERWUNDBAR: Number() verarbeitet hex, aber nicht oktal konsistent
    const value = Number(octetString);

    // "0x41" wird zu 65
    // "010" wird zu 10 (nicht 8 in modernem JS, aber inkonsistent)

    return value >= 0 && value <= 255;
}

// VERWUNDBAR: IP-Adress-Validierung
function vulnerableIsLocalhost(ipString) {
    const parts = ipString.split('.');

    if (parts.length !== 4) return false;

    // VERWUNDBAR: Gibt true für "0177.0.0.1" zurück (oktal für 127)
    // wenn System anders interpretiert
    const firstOctet = parseInt(parts[0], 10);  // Entfernt führende Nullen

    if (firstOctet === 127) {
        return true;  // Denkt es ist localhost
    }

    return false;
}

// Angriff: isLocalhost("0177.0.0.1") gibt true zurück
// Aber Netzwerkanfrage an "0177.0.0.1" geht an 127.0.0.1


// VERWUNDBAR: Konfigurationsanalyse
function vulnerableLoadConfig(config) {
    // Konfigurationsdatei könnte Tippfehler haben: "010" statt "10"
    const timeout = parseInt(config.timeout);

    // VERWUNDBAR: "010" wird zu 8, verursacht unerwartetes Verhalten
    return {
        timeout: timeout * 1000  // Erwartet Millisekunden
    };
}
// VERWUNDBAR: C Zahlenanalyse mit Radix-Problemen

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

// VERWUNDBAR: strtol mit Basis 0 (automatische Erkennung)
int vulnerable_parse_number(const char* str) {
    char* endptr;
    // VERWUNDBAR: Basis 0 erkennt automatisch oktal und hex
    long value = strtol(str, &endptr, 0);

    // "0777" -> 511 (oktal)
    // "0x1FF" -> 511 (hex)
    // "511" -> 511 (dezimal)

    return (int)value;
}

// VERWUNDBAR: IP-Adress-Analyse
int vulnerable_parse_ip(const char* ip_string, uint32_t* ip_addr) {
    // VERWUNDBAR: inet_addr interpretiert oktal
    *ip_addr = inet_addr(ip_string);

    if (*ip_addr == INADDR_NONE) {
        return -1;
    }

    // "0127.0.0.1" wird als 87.0.0.1 interpretiert
    // "0x7f.0.0.1" könnte auf einigen Systemen funktionieren

    return 0;
}

// VERWUNDBAR: Zugangskontrolle basierend auf IP
int vulnerable_is_allowed_ip(const char* client_ip) {
    uint32_t addr;
    if (vulnerable_parse_ip(client_ip, &addr) < 0) {
        return 0;  // Ungültige IP
    }

    // Gegen Blocklist prüfen
    uint32_t localhost = inet_addr("127.0.0.1");

    // VERWUNDBAR: "0177.0.0.1" wird anders analysiert als erwartet
    // Angreifer kann localhost-Beschrankung umgehen
    if (addr == localhost) {
        return 0;  // Localhost blockieren
    }

    return 1;  // Erlauben
}

// Angriff: vulnerable_is_allowed_ip("0177.0.0.1")
// Besteht die Prüfung, aber System verbindet sich mit 127.0.0.1

Sichere Lösung

# SICHER: Sichere Zahlenanalyse mit expliziter Radix-Behandlung

import re
import ipaddress
import subprocess

# SICHER: Korrekte IP-Adress-Validierung und Normalisierung
def safe_validate_ip(ip_string):
    """Validiert und normalisiert IP-Adresse."""
    try:
        # SICHER: ipaddress-Modul verwenden, das Normalisierung handhabt
        ip = ipaddress.ip_address(ip_string)

        # SICHER: Gibt normalisierte Dezimaldarstellung zurück
        return str(ip)
    except ValueError:
        return None

def safe_ping(user_ip):
    """Pingt benutzerbereitgestellte IP-Adresse sicher."""
    # SICHER: IP-Adresse normalisieren
    normalized_ip = safe_validate_ip(user_ip)

    if normalized_ip:
        # SICHER: Normalisierte dezimale IP verwenden
        subprocess.run(['ping', '-c', '1', normalized_ip])
    else:
        print(f"Ungültige IP-Adresse: {user_ip}")

# "0127.0.0.1" -> Gibt None zurück (ungültig) oder normalisierte Form


# SICHER: Strikte IP-Validierung mit verankertem Regex
def safe_validate_ip_regex(ip_string):
    """IP-Validierung mit striktem Pattern-Matching."""
    # SICHER: Verankertes Pattern, nur Dezimalziffern
    pattern = r'^([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])

```javascript
// SICHER: Sichere JavaScript-Zahlenanalyse

// SICHER: Immer Basis angeben
function safeParsePort(portString) {
    // SICHER: Explizite Basis 10
    const port = parseInt(portString, 10);

    // SICHER: Auf NaN prüfen
    if (isNaN(port)) {
        return null;
    }

    if (port > 0 && port < 65536) {
        return port;
    }
    return null;
}

// SICHER: Strikte Zahlenanalyse
function safeParseNumber(str) {
    // SICHER: Hex-Prafix ablehnen
    if (str.toLowerCase().startsWith('0x')) {
        throw new Error('Hexadezimal nicht erlaubt');
    }

    // SICHER: Fuhrende Nullen ablehnen (ausser "0" selbst)
    if (str.length > 1 && str.startsWith('0') && str[1] !== '.') {
        throw new Error('Fuhrende Nullen nicht erlaubt');
    }

    // SICHER: Nur als Dezimal analysieren
    const num = parseInt(str, 10);

    if (isNaN(num)) {
        throw new Error('Ungültige Zahl');
    }

    return num;
}

// SICHER: Korrekte IP-Adress-Validierung
function safeValidateIP(ipString) {
    // SICHER: Striktes Pattern - nur dezimal, keine führenden Nullen
    const pattern = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/;

    if (!pattern.test(ipString)) {
        return null;
    }

    // SICHER: Zusätzliche Validierung - analysieren und re-serialisieren
    const parts = ipString.split('.');
    const normalized = parts.map(p => parseInt(p, 10)).join('.');

    // SICHER: Sicherstellen, dass normalisiert mit Original übereinstimmt (keine führenden Nullen)
    if (normalized !== ipString) {
        return null;  // Hatte führende Nullen
    }

    return normalized;
}

// SICHER: Sichere localhost-Prüfung
function safeIsLocalhost(ipString) {
    const normalized = safeValidateIP(ipString);

    if (!normalized) {
        return false;  // Ungültige IP
    }

    // SICHER: Normalisierte Form prüfen
    const parts = normalized.split('.').map(p => parseInt(p, 10));

    // Auf Loopback-Bereich prüfen (127.0.0.0/8)
    return parts[0] === 127;
}

// SICHER: Konfigurationsanalyse mit Validierung
function safeLoadConfig(config) {
    const timeoutStr = String(config.timeout);

    // SICHER: Verdachtige Eingabe erkennen
    if (timeoutStr.startsWith('0') && timeoutStr.length > 1) {
        console.warn(`Verdachtiger Timeout-Wert: ${timeoutStr}`);
        // Konnte Tippfehler oder beabsichtigtes Oktal sein
    }

    // SICHER: Strikt als Dezimal analysieren
    const timeout = parseInt(timeoutStr, 10);

    if (isNaN(timeout) || timeout <= 0) {
        throw new Error('Ungültiger Timeout-Wert');
    }

    return {
        timeout: timeout * 1000
    };
}
// SICHER: Sichere C-Zahlenanalyse

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <errno.h>

// SICHER: Explizite Dezimalanalyse
int safe_parse_decimal(const char* str, long* result) {
    if (str == NULL || *str == '\0') {
        return -1;
    }

    // SICHER: Hex-Prafix ablehnen
    if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
        return -1;
    }

    // SICHER: Fuhrende Nullen ablehnen (ausser "0" selbst)
    if (str[0] == '0' && str[1] != '\0') {
        return -1;
    }

    // SICHER: Alle Ziffern als dezimal verifizieren
    for (const char* p = str; *p; p++) {
        if (!isdigit(*p)) {
            return -1;
        }
    }

    char* endptr;
    errno = 0;
    *result = strtol(str, &endptr, 10);  // SICHER: Explizite Basis 10

    if (errno != 0 || *endptr != '\0') {
        return -1;
    }

    return 0;
}

// SICHER: Sichere IP-Adress-Analyse
int safe_parse_ip(const char* ip_string, uint32_t* ip_addr) {
    char normalized[16];
    char* parts[4];
    char* str_copy = strdup(ip_string);
    char* token;
    int i = 0;

    // Nach Punkten aufteilen
    token = strtok(str_copy, ".");
    while (token != NULL && i < 4) {
        parts[i++] = token;
        token = strtok(NULL, ".");
    }

    if (i != 4) {
        free(str_copy);
        return -1;
    }

    // SICHER: Jedes Oktett strikt als Dezimal analysieren
    uint8_t octets[4];
    for (i = 0; i < 4; i++) {
        long value;
        if (safe_parse_decimal(parts[i], &value) < 0) {
            free(str_copy);
            return -1;
        }

        if (value < 0 || value > 255) {
            free(str_copy);
            return -1;
        }

        octets[i] = (uint8_t)value;
    }

    free(str_copy);

    // SICHER: Normalisierte IP-Adresse erstellen
    snprintf(normalized, sizeof(normalized), "%d.%d.%d.%d",
             octets[0], octets[1], octets[2], octets[3]);

    // SICHER: Normalisierte Zeichenkette analysieren
    *ip_addr = inet_addr(normalized);

    return 0;
}

// SICHER: Sichere Zugangskontrolle
int safe_is_allowed_ip(const char* client_ip) {
    uint32_t addr;

    // SICHER: Sichere Analyse verwenden
    if (safe_parse_ip(client_ip, &addr) < 0) {
        return 0;  // Ungültige IP - ablehnen
    }

    // SICHER: Loopback-Bereich prüfen (127.0.0.0/8)
    uint8_t first_octet = (addr >> 24) & 0xFF;
    if (first_octet == 127) {
        return 0;  // Localhost-Bereich blockieren
    }

    // SICHER: Private Bereiche prüfen
    // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
    if (first_octet == 10) return 0;
    if (first_octet == 172 && ((addr >> 16) & 0xF0) == 16) return 0;
    if (first_octet == 192 && ((addr >> 16) & 0xFF) == 168) return 0;

    return 1;  // Erlauben
}

CVE-Beispiele

  • CVE-2021-29418: SSRF-Umgehung in Node.js-Anwendung mittels oktaler IP-Notation.
  • CVE-2020-28360: Umgehung der IP-Adress-Validierung mittels alternativer Darstellungen.
  • CVE-2021-29921: Python ipaddress-Modul mit fehlerhafter Behandlung führender Nullen.

Verwandte CWEs

  • CWE-704: Incorrect Type Conversion or Cast (Eltern)
  • CWE-189: Numeric Errors (Kategorie)
  • CWE-20: Improper Input Validation (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1389: Incorrect Parsing of Numbers with Different Radices." https://cwe.mitre.org/data/definitions/1389.html
  2. OWASP. "Server-Side Request Forgery (SSRF)"
  3. RFC 3986. "Uniform Resource Identifier (URI): Generic Syntax"