Numerischer Bereichsvergleich ohne Minimumprüfung
Beschreibung
Numerischer Bereichsvergleich ohne Minimumprüfung ist eine Eingabevalidierungs-Schwachstelle, bei der Software validiert, dass ein numerischer Wert einen Maximalgrenzwert nicht überschreitet, aber nicht verifiziert, dass er einen Minimalgrenzwert erfüllt, wie z.B. nicht-negativ zu sein. Diese Unterlassung ist besonders gefährlich, wenn vorzeichenbehaftete Integer oder Fließkommazahlen in Kontexten verwendet werden, die nur positive Werte erwarten. Negative Werte können unerwartetes Verhalten verursachen, einschließlich Zugriff auf Speicher vor Array-Grenzen, Allokation unerwarteter Speichermengen, Invertierung von Finanztransaktionslogik oder arithmetischem Überlauf bei Konvertierung zu vorzeichenlosen Typen.
Risiko
Diese Schwachstelle kann zu Buffer-Underflows, Speicherbeschädigung und Geschäftslogik-Umgehungen führen. Wenn ein negativer Array-Index verwendet wird, wird Speicher vor dem Array zugegriffen, was möglicherweise zu Informationsoffenlegung oder Codeausführung führt. Negative Werte, die an Speicherallokationsfunktionen übergeben werden, können zu großen Werten wrappen, was Allokationsfehler oder übermäßigen Speicherverbrauch verursacht. In Finanzanwendungen können negative Transaktionsbeträge die beabsichtigte Operation invertieren (Abhebung wird zur Einzahlung). Wenn vorzeichenbehaftete Werte zu vorzeichenlosen gecastet werden, werden negative Werte zu sehr großen positiven Werten, was zu massiven Pufferüberläufen oder Ressourcenerschöpfung führt.
Lösung
Validieren Sie immer sowohl minimale als auch maximale Grenzen für numerische Eingaben. Wenn nur positive Werte gültig sind, prüfen Sie explizit, dass Werte größer oder gleich Null sind. Erwägen Sie die Verwendung vorzeichenloser Integer-Typen (size_t, unsigned int), wenn negative Werte niemals gültig sind. Seien Sie sich impliziter Typkonvertierungen bewusst, die negative Werte maskieren können. Implementieren Sie Validierung an Vertrauensgrenzen, wo externe Daten ins System eintreten. Für Array-Indizes validieren Sie gegen sowohl 0 als auch die Array-Länge. Für Finanzwerte validieren Sie gegen sowohl minimale als auch maximale Transaktionslimits. Verwenden Sie Assertions in Debug-Builds, um Logikfehler zu erkennen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Anwendungsdaten modifizieren - Negative Werte können unerwartete Datenmodifikationen oder Geschäftslogik-Inversionen verursachen. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Ressourcenverbrauch - Negative Werte können übermäßigen Ressourcenverbrauch auslösen, wenn sie konvertiert oder in Berechnungen verwendet werden. |
| Vertraulichkeit, Integrität | Bereich: Vertraulichkeit, Integrität Speicher lesen/modifizieren - Negative Indizes oder Größen können auf Speicher außerhalb der beabsichtigten Grenzen zugreifen. |
Beispielcode
Anfälliger Code
// Anfällig: Keine Minimumprüfung bei Paket-Header-Zähler
void vulnerable_parse_packet(packet_t *packet) {
int numHeaders = packet->headers;
// Prüft nur Maximum, nicht Minimum
if (numHeaders > MAX_HEADERS) {
error("Zu viele Headers");
return;
}
// Anfällig: Negatives numHeaders verursacht Probleme
// Wenn zu size_t für malloc gecastet, wird es zu riesiger positiver Zahl
PacketHeader *headers = malloc(numHeaders * sizeof(PacketHeader));
// Wenn Allokation mit beschädigter Größe "erfolgreich", tritt Überlauf auf
for (int i = 0; i < numHeaders; i++) {
parse_header(&headers[i], packet);
}
}
// Anfällig: Array-Zugriff ohne Prüfung auf negativen Index
int vulnerable_get_element(int *array, int length, int index) {
// Prüft nur obere Grenze
if (index < length) {
return array[index]; // Negativer Index liest vor dem Array!
}
return -1;
}
// Angriff: index = -5 liest Speicher 5 Positionen vor dem Array
// Anfällig: Finanzabhebung ohne Prüfung auf negative Werte
public class VulnerableBank {
public boolean withdraw(Account account, double amount) {
// Prüft nur maximales Abhebungslimit
if (amount > MAXIMUM_WITHDRAWAL_LIMIT) {
return false;
}
// Anfällig: Negativer Betrag verursacht Einzahlung stattdessen!
account.setBalance(account.getBalance() - amount);
// -(-100) = +100, also "Abheben" von -100 fügt 100 hinzu
return true;
}
}
# Anfällig: Substring ohne Prüfung auf negative Länge
def vulnerable_substring(string, start, length):
# Prüft nur maximale Länge
if length > MAX_SUBSTRING_LENGTH:
length = MAX_SUBSTRING_LENGTH
# Anfällig: Negative Länge oder Start verursacht Probleme
return string[start:start + length]
# Python behandelt dies gnädig, aber in C:
# memcpy(dest, src + start, length) mit negativer Länge ist UB
// Anfällig: Schleifenzähler ohne Prüfung auf negative Werte
void vulnerable_copy_elements(int *dest, int *src, int count) {
// Prüft nur Maximum
if (count > MAX_ELEMENTS) {
count = MAX_ELEMENTS;
}
// Anfällig: Negativer count
// Im unsigned-Vergleich wird -1 zu riesiger positiver Zahl
for (size_t i = 0; i < (size_t)count; i++) {
dest[i] = src[i]; // Massiver Buffer Overflow!
}
}
// Anfällig: Array-Slice ohne Validierung negativer Werte
function vulnerableSlice(array, start, count) {
// Validiert count nur gegen Array-Länge
if (count > array.length) {
count = array.length;
}
// Anfällig: Negativer start oder count
// JavaScript behandelt gnädig, aber Geschäftslogik möglicherweise nicht
return array.slice(start, start + count);
}
// Anfällig: Größe verwendet in Speicherallokation
int vulnerable_allocate(int requested_size) {
// Prüft nur Maximum
if (requested_size > MAX_ALLOCATION_SIZE) {
return -1;
}
// Anfällig: Negative Größe
// malloc((size_t)-1) fordert ~4GB auf 32-Bit-Systemen an
char *buffer = malloc(requested_size);
if (!buffer) {
return -1;
}
memset(buffer, 0, requested_size); // Absturz oder Beschädigung
return 0;
}
Korrigierter Code
// Korrigiert: Sowohl minimale als auch maximale Grenzen prüfen
void fixed_parse_packet(packet_t *packet) {
int numHeaders = packet->headers;
// Korrigiert: Beide Grenzen prüfen
if (numHeaders < 0 || numHeaders > MAX_HEADERS) {
error("Ungültige Header-Anzahl");
return;
}
// Jetzt sicher zu allokieren
PacketHeader *headers = malloc((size_t)numHeaders * sizeof(PacketHeader));
if (!headers) {
error("Allokation fehlgeschlagen");
return;
}
for (int i = 0; i < numHeaders; i++) {
parse_header(&headers[i], packet);
}
}
// Korrigiert: Array-Index-Bereich validieren
int fixed_get_element(int *array, int length, int index) {
// Korrigiert: Beide Grenzen prüfen
if (index < 0 || index >= length) {
return -1; // Ungültiger Index
}
return array[index];
}
// Besser: Vorzeichenlose Typen verwenden wenn negative Werte ungültig sind
int better_get_element(int *array, size_t length, size_t index) {
// size_t ist vorzeichenlos, also negative Werte unmöglich
if (index >= length) {
return -1;
}
return array[index];
}
// Korrigiert: Transaktionsbetragsgrenzen validieren
public class FixedBank {
private static final double MINIMUM_TRANSACTION = 0.01;
public boolean withdraw(Account account, double amount) {
// Korrigiert: Sowohl Minimum als auch Maximum prüfen
if (amount < MINIMUM_TRANSACTION) {
return false; // Lehnt negative und Null-Beträge ab
}
if (amount > MAXIMUM_WITHDRAWAL_LIMIT) {
return false;
}
if (amount > account.getBalance()) {
return false; // Unzureichendes Guthaben
}
account.setBalance(account.getBalance() - amount);
return true;
}
}
# Korrigiert: Beide Grenzen für Substring validieren
def fixed_substring(string, start, length):
# Korrigiert: Alle Parameter validieren
if start < 0:
start = 0
if length < 0:
return "" # Oder ValueError auslösen
if start > len(string):
return ""
# Am String-Ende begrenzen
end = min(start + length, len(string))
return string[start:end]
# Besser: Explizite Validierung mit Exceptions verwenden
def better_substring(string, start, length):
if not isinstance(start, int) or not isinstance(length, int):
raise TypeError("start und length müssen Integer sein")
if start < 0 or length < 0:
raise ValueError("start und length müssen nicht-negativ sein")
if start > len(string):
raise IndexError("start überschreitet String-Länge")
return string[start:start + length]
// Korrigiert: Vorzeichenlosen Typ für count verwenden
void fixed_copy_elements(int *dest, int *src, size_t count) {
// Korrigiert: Mit size_t sind negative Werte unmöglich
// Aber trotzdem Maximum validieren
if (count > MAX_ELEMENTS) {
count = MAX_ELEMENTS;
}
// Sicher: count ist garantiert nicht-negativ
for (size_t i = 0; i < count; i++) {
dest[i] = src[i];
}
}
// Wenn vorzeichenbehafteter Typ von API erforderlich:
void fixed_copy_signed(int *dest, int *src, int count) {
// Korrigiert: Explizite Validierung
if (count < 0) {
return; // Ungültiger count
}
if (count > MAX_ELEMENTS) {
count = MAX_ELEMENTS;
}
for (int i = 0; i < count; i++) {
dest[i] = src[i];
}
}
// Korrigiert: Ordnungsgemäße Größenvalidierung für Allokation
int fixed_allocate(int requested_size) {
// Korrigiert: Beide Grenzen prüfen
if (requested_size < 0) {
return -1; // Ungültige Größe
}
if (requested_size > MAX_ALLOCATION_SIZE) {
return -1;
}
// Sicherer Cast: wir haben nicht-negativ verifiziert
char *buffer = malloc((size_t)requested_size);
if (!buffer) {
return -1;
}
memset(buffer, 0, (size_t)requested_size);
return 0;
}
// Besser: size_t von Anfang an verwenden
int better_allocate(size_t requested_size) {
// size_t ist vorzeichenlos, negative Werte unmöglich
if (requested_size > MAX_ALLOCATION_SIZE) {
return -1;
}
char *buffer = malloc(requested_size);
if (!buffer) {
return -1;
}
memset(buffer, 0, requested_size);
return 0;
}
// Korrigiert: Umfassendes Validierungs-Utility
public class NumericValidator {
public static void validateInRange(int value, int min, int max, String name) {
if (value < min || value > max) {
throw new IllegalArgumentException(
String.format("%s muss zwischen %d und %d liegen, war %d",
name, min, max, value));
}
}
public static void validateNonNegative(int value, String name) {
if (value < 0) {
throw new IllegalArgumentException(
name + " muss nicht-negativ sein, war " + value);
}
}
public static void validatePositive(int value, String name) {
if (value <= 0) {
throw new IllegalArgumentException(
name + " muss positiv sein, war " + value);
}
}
}
// Verwendung:
public void processData(int count, int index) {
NumericValidator.validateNonNegative(count, "count");
NumericValidator.validateInRange(index, 0, count - 1, "index");
// Jetzt sicher zu verwenden
}
Verwandte CWEs
- CWE-1023: Unvollständiger Vergleich mit fehlenden Faktoren (Eltern)
- CWE-119: Unzureichende Einschränkung von Operationen innerhalb der Grenzen eines Speicherpuffers (kann folgen)
- CWE-124: Puffer-Unterschreitung ('Buffer Underflow') (kann folgen)
- CWE-195: Signed-zu-Unsigned-Konvertierungsfehler (kann folgen)
- CWE-129: Unzureichende Validierung von Array-Index (verwandt)
Referenzen
- MITRE Corporation. "CWE-839: Numeric Range Comparison Without Minimum Check." https://cwe.mitre.org/data/definitions/839.html
- CERT C Secure Coding Standard. "INT04-C. Enforce limits on integer values originating from tainted sources."
- CERT C Secure Coding Standard. "ARR30-C. Do not form or use out-of-bounds pointers or array subscripts."