Sicherheit von C++-Anwendungen: Top-10 Schwachstellen und Schutzmaßnahmen
C++ ist eine leistungsstarke systemnahe Programmiersprache, aber ihre Flexibilität und der direkte Speicherzugriff machen Anwendungen anfällig für schwerwiegende Angriffe. Laut OWASP und CWE entstehen viele kritische Schwachstellen in industrieller Software (von Spielen bis zur Avionik) genau aufgrund von Fehlern im C++-Code. In diesem Artikel analysieren wir die Top-10 der gefährlichsten Schwachstellen, zeigen Codebeispiele und erläutern, wie man sie vermeidet.
1. Pufferüberlauf (Buffer Overflow)
Eine klassische Schwachstelle, die es einem Angreifer ermöglicht, benachbarte Speicherbereiche zu überschreiben. Sie tritt beim Kopieren von Daten ohne Größenprüfung auf.
// Schwachstellen-Code#include <cstring>void vulnerable(char* input) { char buffer[10]; strcpy(buffer, input); // Gefahr: wenn input länger als 9 Zeichen ist}// Sichere Variantevoid secure(char* input, size_t len) { char buffer[10]; if (len < sizeof(buffer)) { strncpy(buffer, input, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\\0'; }}Schutz: Verwenden Sie std::string, std::vector oder Funktionen mit Längenbegrenzung (strncpy, snprintf). Aktivieren Sie die Compiler-Flags -fstack-protector-strong und -D_FORTIFY_SOURCE=2.
2. Nutzung nach Freigabe (Use-After-Free)
Zugriff auf Speicher, der bereits freigegeben wurde. Führt zum Lesen/Schreiben in fremde Bereiche oder zur Ausführung von beliebigem Code.
// Schwachstellen-Codeint* ptr = new int(42);delete ptr;*ptr = 100; // Fehler! ptr ist ungültig// Sichere Varianteint* ptr = new int(42);delete ptr;ptr = nullptr; // Zeiger zurücksetzenif (ptr) *ptr = 100; // Prüfung vor VerwendungSchutz: Verwenden Sie intelligente Zeiger (std::unique_ptr, std::shared_ptr), setzen Sie rohe Zeiger nach delete zurück, nutzen Sie Sanitizer (-fsanitize=address).
3. Ganzzahlüberlauf (Integer Overflow)
Überschreitung des zulässigen Wertebereichs bei arithmetischen Operationen. Wird oft zur Umgehung von Puffergrößenprüfungen verwendet.
// Schwachstellen-Codesize_t size = 0;size_t user_input = 4294967295; // 2^32 - 1size_t total = size + user_input + 1; // Überlauf!// Sichere Variante mit Prüfungif (user_input > SIZE_MAX - size) { throw std::overflow_error("Größe zu groß");}size_t total = size + user_input + 1;Schutz: Verwenden Sie Typen mit fester Bitbreite (uint32_t), prüfen Sie den Überlauf vor der Operation, nutzen Sie __builtin_add_overflow (GCC/Clang).
4. Dereferenzierung von Nullzeigern (Null Pointer Dereference)
Versuch, auf Speicher an der Adresse NULL zuzugreifen. Führt zum Programmabsturz (segfault) oder zu einer DoS-Schwachstelle.
// Schwachstellen-Codevoid process(int* ptr) { *ptr = 10; // Wenn ptr == nullptr — Absturz}// Sichere Variantevoid process(int* ptr) { if (!ptr) { throw std::invalid_argument("Nullzeiger empfangen"); } *ptr = 10;}Schutz: Prüfen Sie Zeiger vor der Verwendung immer auf nullptr, geben Sie std::optional anstelle von rohen Zeigern zurück.
5. Speicherlecks (Memory Leaks)
Verlust des Zugriffs auf allokierten Speicher ohne dessen Freigabe. Erschöpft mit der Zeit die Systemressourcen.
// Schwachstellen-Codevoid leak() { int* data = new int[100]; // delete[] data vergessen — Speicherleck}// Sichere Variantevoid no_leak() { std::unique_ptr<int[]> data = std::make_unique<int[]>(100); // Speicher wird automatisch freigegeben}Schutz: Verwenden Sie RAII (intelligente Zeiger, Container