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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Angreifer können mit unerwarteten Zahlenbasen auf private Ressourcen zugreifen. |
| Integritat | Bereich: 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
- MITRE Corporation. "CWE-1389: Incorrect Parsing of Numbers with Different Radices." https://cwe.mitre.org/data/definitions/1389.html
- OWASP. "Server-Side Request Forgery (SSRF)"
- RFC 3986. "Uniform Resource Identifier (URI): Generic Syntax"