Unsachgemäße Kontrolle der sequentiellen Speicherallokation
Beschreibung
Unsachgemäße Kontrolle der sequentiellen Speicherallokation tritt auf, wenn ein Produkt Ressourcen sequentiell allokiert, ohne Grenzen oder Drosselung zu setzen, was zu unkontrolliertem Ressourcenverbrauch führen kann. Wenn ein Angreifer viele Ressourcen-Allokationen auslösen kann, ohne entsprechende Freigabe, kann er den verfügbaren Speicher oder andere Systemressourcen erschöpfen, was zu Denial of Service führt. Dieses Problem tritt häufig bei Speicherallokationen, Datei-Handle-Zuweisungen oder Verbindungspools auf, bei denen das System die kumulative Ressourcennutzung nicht begrenzt.
Risiko
Unkontrollierte sequentielle Allokation erzeugt erhebliche Risiken. Speichererschöpfungsangriffe können Anwendungen oder Systeme zum Absturz bringen. Ressourcenaushungerung beeinträchtigt andere Prozesse auf dem System. Angreifer können niedrige Allokationsraten verwenden, um der Erkennung zu entgehen, während sie allmählich Ressourcen erschöpfen. Die kumulative Wirkung vieler kleiner Allokationen kann schwerer zu erkennen sein als einzelne große Allokationen.
Lösung
Implementieren Sie Grenzen für die Gesamtressourcen-Allokation pro Benutzer oder Sitzung. Verwenden Sie Speicherpools mit festen Größen. Setzen Sie Timeouts für Ressourcen-Haltedauer. Implementieren Sie Überwachung für Ressourcenverbrauchsmuster. Verwenden Sie Ratenbegrenzung für ressourcenintensive Operationen. Stellen Sie die ordnungsgemäße Ressourcenfreigabe in allen Codepfaden sicher.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Ressourcenverbrauch - Angreifer können verfügbare Systemressourcen durch wiederholte Allokation erschöpfen. |
Beispielcode und Lösung
Verwundbarer Code
// Verwundbar: Unbegrenzte Speicherallokation in C
#include <stdlib.h>
#include <string.h>
// VERWUNDBAR: Keine Begrenzung der Allokationsanzahl
struct Nachricht* nachrichten_liste = NULL;
int nachrichten_anzahl = 0;
// VERWUNDBAR: Jeder Aufruf allokiert ohne Grenze
void nachricht_hinzufügen(const char* inhalt) {
struct Nachricht* neu = malloc(sizeof(struct Nachricht));
neu->inhalt = strdup(inhalt); // Weitere Allokation ohne Prüfung
neu->nächste = nachrichten_liste;
nachrichten_liste = neu;
nachrichten_anzahl++; // Keine obere Grenze!
}
// VERWUNDBAR: Unbegrenzte Puffer-Wachstum
char* verwundbar_lese_alles(int fd) {
char* puffer = NULL;
size_t gesamt = 0;
char temp[1024];
ssize_t gelesen;
while ((gelesen = read(fd, temp, sizeof(temp))) > 0) {
// VERWUNDBAR: Unbegrenztes Wachstum
puffer = realloc(puffer, gesamt + gelesen);
memcpy(puffer + gesamt, temp, gelesen);
gesamt += gelesen;
}
return puffer;
}
# Verwundbar: Unbegrenzte Sammlung in Python
class VerwundbarerCache:
def __init__(self):
# VERWUNDBAR: Unbegrenzter Cache
self.cache = {}
def hinzufügen(self, schlüssel, wert):
# VERWUNDBAR: Keine Grenze für Cache-Einträge
self.cache[schlüssel] = wert
def holen(self, schlüssel):
return self.cache.get(schlüssel)
# VERWUNDBAR: Unbegrenzte Listen-Akkumulation
class VerwundbarerLogger:
def __init__(self):
self.logs = [] # Wächst unbegrenzt
def log(self, nachricht):
# VERWUNDBAR: Logs werden nie bereinigt
self.logs.append({
'zeit': time.time(),
'nachricht': nachricht
})
# VERWUNDBAR: Unbegrenzte Sitzungsspeicherung
sitzungen = {}
def sitzung_erstellen(benutzer_id):
# VERWUNDBAR: Alte Sitzungen werden nie entfernt
sitzung_id = generiere_sitzung_id()
sitzungen[sitzung_id] = {
'benutzer': benutzer_id,
'daten': {}
}
return sitzung_id
Sichere Lösung
// Sicher: Begrenzte Speicherallokation in C
#include <stdlib.h>
#include <string.h>
#define MAX_NACHRICHTEN 1000
#define MAX_NACHRICHT_GROESSE 4096
#define MAX_PUFFER_GROESSE (10 * 1024 * 1024) // 10MB
struct Nachricht* nachrichten_liste = NULL;
int nachrichten_anzahl = 0;
// Sicher: Begrenzte Allokation
int nachricht_hinzufügen(const char* inhalt) {
// Sicher: Grenze prüfen
if (nachrichten_anzahl >= MAX_NACHRICHTEN) {
return -1; // Grenze erreicht
}
// Sicher: Nachrichtengröße begrenzen
size_t länge = strlen(inhalt);
if (lange > MAX_NACHRICHT_GROESSE) {
return -1; // Zu groß
}
struct Nachricht* neu = malloc(sizeof(struct Nachricht));
if (!neu) return -1;
neu->inhalt = malloc(lange + 1);
if (!neu->inhalt) {
free(neu);
return -1;
}
memcpy(neu->inhalt, inhalt, länge + 1);
neu->nächste = nachrichten_liste;
nachrichten_liste = neu;
nachrichten_anzahl++;
return 0;
}
// Sicher: Begrenztes Pufferlesen
char* sicher_lese_begrenzt(int fd, size_t max_große) {
if (max_große > MAX_PUFFER_GROESSE) {
max_große = MAX_PUFFER_GROESSE;
}
char* puffer = malloc(max_große);
if (!puffer) return NULL;
size_t gesamt = 0;
char temp[1024];
ssize_t gelesen;
while ((gelesen = read(fd, temp, sizeof(temp))) > 0) {
// Sicher: Grenze prüfen
if (gesamt + gelesen > max_große) {
free(puffer);
return NULL; // Würde Grenze überschreiten
}
memcpy(puffer + gesamt, temp, gelesen);
gesamt += gelesen;
}
return puffer;
}
# Sicher: Begrenzter Cache in Python
from collections import OrderedDict
import time
import threading
class SichererCache:
def __init__(self, max_große=1000, ttl_sekunden=3600):
self.max_große = max_große
self.ttl = ttl_sekunden
self.cache = OrderedDict()
self.lock = threading.Lock()
def hinzufügen(self, schlüssel, wert):
with self.lock:
# Sicher: Abgelaufene Einträge entfernen
self._bereinigen()
# Sicher: LRU-Räumung wenn voll
while len(self.cache) >= self.max_große:
self.cache.popitem(last=False)
self.cache[schlüssel] = {
'wert': wert,
'erstellt': time.time()
}
def _bereinigen(self):
jetzt = time.time()
abgelaufen = [k for k, v in self.cache.items()
if jetzt - v['erstellt'] > self.ttl]
for k in abgelaufen:
del self.cache[k]
# Sicher: Begrenzter Sitzungsmanager
class SichererSitzungsManager:
MAX_SITZUNGEN = 10000
SITZUNGS_TIMEOUT = 3600
def __init__(self):
self.sitzungen = {}
self.lock = threading.Lock()
def sitzung_erstellen(self, benutzer_id):
with self.lock:
# Sicher: Alte Sitzungen bereinigen
self._abgelaufene_bereinigen()
# Sicher: Grenze erzwingen
if len(self.sitzungen) >= self.MAX_SITZUNGEN:
# Älteste Sitzungen entfernen
self._aelteste_entfernen(100)
sitzung_id = self._generiere_id()
self.sitzungen[sitzung_id] = {
'benutzer': benutzer_id,
'erstellt': time.time(),
'letzter_zugriff': time.time()
}
return sitzung_id
def _abgelaufene_bereinigen(self):
jetzt = time.time()
abgelaufen = [sid for sid, data in self.sitzungen.items()
if jetzt - data['letzter_zugriff'] > self.SITZUNGS_TIMEOUT]
for sid in abgelaufen:
del self.sitzungen[sid]
Ausgenutzt in der Praxis
Speichererschöpfungs-DoS in Webservern
Angreifer haben Speichererschöpfungsangriffe gegen Webserver durchgeführt, indem sie viele kleine Anfragen sendeten, die jeweils Speicher allokierten, ohne ihn freizugeben, was zum Serverabsturz führte.
ZIP-Bomben und Dekomprimierungsangriffe
Komprimierte Dateien, die zu riesigen Datenmengen dekomprimieren, nutzen unbegrenzte Allokation aus, um Systemspeicher zu erschöpfen.
Tools zum Testen und Ausnutzen
-
Valgrind - Speicherfehler und Lecks erkennen.
-
AddressSanitizer - Speicherfehler-Detektor.
-
memory_profiler - Python-Speicherprofiler.
CVE-Beispiele
-
CVE-2018-6389 - WordPress Load-Scripts-DoS über unbegrenzte Ressourcenladung.
-
CVE-2020-8908 - Guava erstellt temporäre Verzeichnisse ohne Grenzen.
Referenzen
-
MITRE Corporation. "CWE-1325: Improperly Controlled Sequential Memory Allocation." https://cwe.mitre.org/data/definitions/1325.html
-
OWASP. "Memory Exhaustion." https://owasp.org/www-community/vulnerabilities/Memory_Exhaustion
-
CWE. "CWE-400: Uncontrolled Resource Consumption." https://cwe.mitre.org/data/definitions/400.html