Verwendung potenziell gefährlicher Funktionen
Beschreibung
Verwendung potenziell gefährlicher Funktionen ist eine Schwachstelle, bei der ein Programm Bibliotheksfunktionen aufruft, die bei unsachgemäßer Verwendung Sicherheitslücken einführen können, obwohl sie mit entsprechenden Vorsichtsmaßnahmen sicher verwendet werden können. Im Gegensatz zu inhärent gefährlichen Funktionen (CWE-242), die nie als sicher garantiert werden können, bergen diese Funktionen hauptsächlich durch unsachgemäße Verwendung Risiken. Das klassische Beispiel ist strcpy() — es funktioniert sicher, wenn der Zielpuffer größer als die Quelle ist, aber sein häufiger Missbrauch hat viele sicherheitsbewusste Organisationen dazu veranlasst, es vollständig zu verbieten.
Risiko
Die Verwendung potenziell gefährlicher Funktionen ohne entsprechende Vorsichtsmaßnahmen führt zu Pufferüberläufen, Format-String-Schwachstellen und anderen Speicherkorruptionsproblemen. Buffer Overflow-Schwachstellen ermöglichen Angreifern die Ausführung beliebigen Codes, Anwendungsabstürze oder Privilegieneskalation. Selbst wenn Entwickler glauben, die Eingabe validiert zu haben, können Randfälle oder zukünftige Codeänderungen Schwachstellen einführen. Die hohe Häufigkeit des Missbrauchs macht diese Funktionen zu Zielen für Sicherheitsprüfer und Angreifer gleichermaßen.
Lösung
Führen Sie eine Liste verbotener oder eingeschränkter Funktionen mit dokumentierten sichereren Alternativen. Verwenden Sie sicherere Ersatzfunktionen: strncpy() oder strlcpy() statt strcpy(), snprintf() statt sprintf(), fgets() statt gets(). Bei Verwendung eingeschränkter Funktionen implementieren Sie strenge Eingabevalidierung und Puffergrößenprüfung. Aktivieren Sie Compiler-Warnungen, die gefährliche Funktionsnutzung markieren. Verwenden Sie statische Analysetools zur Erkennung verbotener Funktionen während der Entwicklung.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Absturz - Pufferüberläufe durch Missbrauch gefährlicher Funktionen können Anwendungen zum Absturz bringen. |
| Integrität | Bereich: Integrität Datenmodifikation - Speicherkorruption kann Programmdaten und -verhalten verändern. |
| Vertraulichkeit | Bereich: Vertraulichkeit, Integrität, Verfügbarkeit Codeausführung - Buffer Overflow-Ausnutzung ermöglicht beliebige Codeausführung. |
Beispielcode und Lösung
Verwundbarer Code
// Verwundbar: Verwendung von strcpy ohne Grenzenprüfung
#include <string.h>
#include <stdio.h>
void verwundbar_strcpy(char* eingabe) {
char buffer[64];
// Verwundbar: Keine Grenzenprüfung - Buffer Overflow wenn Eingabe > 63 Zeichen
strcpy(buffer, eingabe);
printf("Kopiert: %s\n", buffer);
}
// Verwundbar: Verwendung von sprintf ohne Grenzenprüfung
void verwundbar_sprintf(const char* name, int id) {
char nachricht[128];
// Verwundbar: Könnte überlaufen wenn Name sehr lang ist
sprintf(nachricht, "Benutzer: %s, ID: %d, Status: Aktiv, Rolle: Administrator",
name, id);
log_message(nachricht);
}
// Verwundbar: Verwendung von gets (immer gefährlich)
void verwundbar_gets() {
char eingabe[100];
printf("Befehl eingeben: ");
// Verwundbar: gets() hat überhaupt keine Grenzenprüfung
gets(eingabe); // NIEMALS VERWENDEN - veraltet und in C11 entfernt
verarbeite_befehl(eingabe);
}
// Verwundbar: Verwendung von scanf mit %s
void verwundbar_scanf() {
char benutzername[32];
printf("Benutzername: ");
// Verwundbar: %s hat keine Längenbegrenzung
scanf("%s", benutzername); // Buffer Overflow möglich
authentifiziere(benutzername);
}
// Verwundbar: Verwendung von strcat ohne Grenzenprüfung
void verwundbar_strcat(char* prefix, char* suffix) {
char ergebnis[64];
strcpy(ergebnis, prefix);
// Verwundbar: Keine Prüfung ob Ergebnis Platz für Suffix hat
strcat(ergebnis, suffix); // Buffer Overflow wenn kombinierte Länge > 63
verwende_ergebnis(ergebnis);
}
// Verwundbar: Verwendung von vsprintf
void verwundbar_log(const char* format, ...) {
char buffer[256];
va_list args;
va_start(args, format);
// Verwundbar: Keine Grenzenprüfung
vsprintf(buffer, format, args); // Überlauf möglich
va_end(args);
write_log(buffer);
}
// Verwundbar: Verwendung von realpath mit festem Buffer (auf manchen Systemen)
void verwundbar_realpath(const char* path) {
char resolved[PATH_MAX];
// Manche Implementierungen können überlaufen wenn PATH_MAX unzureichend
// oder wenn der aufgelöste Pfad die Buffergröße überschreitet
char* result = realpath(path, resolved);
if (result) {
process_path(resolved);
}
}
Sichere Lösung
// Sicher: Verwendung von strncpy/strlcpy mit Grenzenprüfung
#include <string.h>
#include <stdio.h>
// Sicher: Sichere String-Kopie mit expliziten Grenzen
void sichere_string_kopie(char* eingabe) {
char buffer[64];
// Option 1: strncpy (Hinweis: terminiert möglicherweise nicht mit Null!)
strncpy(buffer, eingabe, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0'; // Null-Terminierung sicherstellen
// Option 2: strlcpy (BSD/moderne Systeme - bevorzugt)
// size_t result = strlcpy(buffer, eingabe, sizeof(buffer));
// if (result >= sizeof(buffer)) {
// // Kürzung aufgetreten
// behandle_kürzung();
// }
printf("Kopiert: %s\n", buffer);
}
// Sicher: Verwendung von snprintf mit Grenzenprüfung
void sichere_sprintf(const char* name, int id) {
char nachricht[128];
// Sicher: snprintf respektiert Puffergrenzen
int geschrieben = snprintf(nachricht, sizeof(nachricht),
"Benutzer: %s, ID: %d, Status: Aktiv, Rolle: Administrator",
name, id);
if (geschrieben >= (int)sizeof(nachricht)) {
// Kürzung aufgetreten - entsprechend behandeln
behandle_kürzung();
}
if (geschrieben > 0) {
log_message(nachricht);
}
}
// Sicher: Verwendung von fgets statt gets
void sichere_gets() {
char eingabe[100];
printf("Befehl eingeben: ");
// Sicher: fgets hat Grenzenprüfung und behandelt Newline
if (fgets(eingabe, sizeof(eingabe), stdin) != NULL) {
// Abschließenden Newline entfernen wenn vorhanden
size_t len = strlen(eingabe);
if (len > 0 && eingabe[len - 1] == '\n') {
eingabe[len - 1] = '\0';
}
verarbeite_befehl(eingabe);
}
}
// Sicher: Verwendung von scanf mit Breitenangabe
void sichere_scanf() {
char benutzername[32];
printf("Benutzername: ");
// Sicher: Breitenangabe begrenzt Eingabelänge
if (scanf("%31s", benutzername) == 1) { // 31 = Größe - 1 für Null
authentifiziere(benutzername);
}
// Besser: fgets für Benutzereingabe verwenden
// if (fgets(benutzername, sizeof(benutzername), stdin)) {
// benutzername[strcspn(benutzername, "\n")] = '\0';
// authentifiziere(benutzername);
// }
}
// Sicher: Verwendung von strncat mit Grenzenprüfung
void sichere_strcat(char* prefix, char* suffix) {
char ergebnis[64];
size_t prefix_len = strlen(prefix);
size_t suffix_len = strlen(suffix);
// Sicher: Gesamtlänge vor Verkettung prüfen
if (prefix_len + suffix_len >= sizeof(ergebnis)) {
behandle_fehler("Kombinierter String zu lang");
return;
}
strcpy(ergebnis, prefix); // Sicher: Längen geprüft
strcat(ergebnis, suffix); // Sicher: Längen geprüft
// Oder strlcat verwenden:
// strlcpy(ergebnis, prefix, sizeof(ergebnis));
// strlcat(ergebnis, suffix, sizeof(ergebnis));
verwende_ergebnis(ergebnis);
}
// Sicher: Verwendung von vsnprintf
void sicheres_log(const char* format, ...) {
char buffer[256];
va_list args;
va_start(args, format);
// Sicher: vsnprintf respektiert Grenzen
int geschrieben = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
if (geschrieben > 0 && geschrieben < (int)sizeof(buffer)) {
write_log(buffer);
} else if (geschrieben >= (int)sizeof(buffer)) {
// Kürzung - loggen was vorhanden ist
write_log(buffer);
write_log("[gekürzt]");
}
}
// Sicher: Verwendung von realpath auf sichere Weise
void sicherer_realpath(const char* path) {
// Option 1: realpath allokieren lassen (POSIX.1-2008)
char* resolved = realpath(path, NULL);
if (resolved) {
process_path(resolved);
free(resolved); // Allozierten Speicher freigeben
}
// Option 2: PATH_MAX Buffer mit Validierung verwenden
// char resolved[PATH_MAX];
// char* result = realpath(path, resolved);
// if (result) {
// process_path(resolved);
// }
}
// Beispiel: Sichere String-Bibliothek (konzeptionell)
typedef struct {
char* data;
size_t length;
size_t capacity;
} SafeString;
SafeString* safe_string_create(size_t initial_capacity) {
SafeString* ss = malloc(sizeof(SafeString));
if (ss) {
ss->data = malloc(initial_capacity + 1);
if (ss->data) {
ss->data[0] = '\0';
ss->length = 0;
ss->capacity = initial_capacity;
} else {
free(ss);
ss = NULL;
}
}
return ss;
}
int safe_string_copy(SafeString* dest, const char* src) {
size_t src_len = strlen(src);
if (src_len > dest->capacity) {
// Reallokieren oder Fehler zurückgeben
char* new_data = realloc(dest->data, src_len + 1);
if (!new_data) return -1;
dest->data = new_data;
dest->capacity = src_len;
}
memcpy(dest->data, src, src_len + 1);
dest->length = src_len;
return 0;
}
void safe_string_free(SafeString* ss) {
if (ss) {
free(ss->data);
free(ss);
}
}
// Referenz-Header für verbotene Funktionen (wie Microsofts banned.h)
#ifndef _BANNED_H_
#define _BANNED_H_
// Vollständig verboten - keine sichere Verwendung
#pragma deprecated(gets)
#pragma deprecated(gets_s) // Immer noch problematisch
// Bedingt verboten - sicherere Alternativen verwenden
#ifndef ALLOW_DANGEROUS_FUNCTIONS
#pragma deprecated(strcpy, "Verwende strlcpy oder strncpy stattdessen")
#pragma deprecated(strcat, "Verwende strlcat oder strncat stattdessen")
#pragma deprecated(sprintf, "Verwende snprintf stattdessen")
#pragma deprecated(vsprintf, "Verwende vsnprintf stattdessen")
#pragma deprecated(scanf, "Verwende fgets oder gib Breite an")
#pragma deprecated(sscanf, "Breitenlimits angeben")
#pragma deprecated(strncpy, "Verwende strlcpy für sicherere Semantik")
#pragma deprecated(strncat, "Verwende strlcat für sicherere Semantik")
#endif
#endif // _BANNED_H_
Ausgenutzt in der Praxis
Heartbleed (OpenSSL, 2014)
Obwohl technisch eine Bounds-Check-Schwachstelle, wurde die Heartbleed-Schwachstelle durch unsichere Speicheroperationen ohne ordnungsgemäße Längenvalidierung verursacht. Millionen von Servern waren betroffen, und sensible Daten einschließlich privater Schlüssel wurden offengelegt.
Morris-Wurm (Internet, 1988)
Der erste bedeutende Internet-Wurm nutzte einen Buffer-Overflow in der gets()-Funktion des fingerd-Daemons aus. Dies führte zur Infektion von etwa 6.000 Computern und verursachte Millionenschäden.
Sendmail-Schwachstellen (diverse, 1990er-2000er)
Sendmail hatte zahlreiche Buffer Overflow-Schwachstellen aufgrund der Verwendung von strcpy() und sprintf() ohne ordnungsgemäße Grenzenprüfung. Diese wurden wiederholt für Remote-Code-Ausführung ausgenutzt.
Tools zum Testen und Ausnutzen
-
Flawfinder — Statisches Analysetool zur Erkennung gefährlicher C/C++-Funktionen.
-
cppcheck — Statischer Analyser für C/C++, der unsichere Funktionsnutzung erkennt.
-
Microsoft Visual Studio Code Analysis — Integrierte Analyse mit banned.h-Unterstützung.
CVE-Beispiele
-
CVE-2007-1470 — Mehrere Pufferüberläufe durch sprintf() und strcpy().
-
CVE-2009-3849 — Buffer Overflow durch strcat()-Verwendung.
-
CVE-2011-0712 — strcpy()-Schwachstelle durch strlcpy() als Fix ersetzt.
Referenzen
-
MITRE Corporation. "CWE-676: Use of Potentially Dangerous Function." https://cwe.mitre.org/data/definitions/676.html
-
Microsoft. "Security Development Lifecycle (SDL) Banned Function Calls." https://docs.microsoft.com/en-us/previous-versions/bb288454(v=msdn.10)
-
CERT C Coding Standard. "STR07-C: Use the bounds-checking interfaces for string manipulation." https://wiki.sei.cmu.edu/confluence/display/c/STR07-C.+Use+the+bounds-checking+interfaces+for+string+manipulation