Dokumentieren von Sicherheitsaspekten in C-Runtime und Windows-APIs

Veröffentlicht: 09. Dez 2001 | Aktualisiert: 07. Nov 2004

Von Michael Howard

Zusammenfassung:
In diesem Artikel werden häufige Fehler bei Funktionsaufrufen mit C und C++ und ihren Sicherheitsänderungen sowie die empfohlene Verwendung bestimmter Funktionen beschrieben. Dies ist ein fortlaufender Prozess. In den kommenden Monaten werden wir zahlreiche APIs um Sicherheitsinformationen erweitern.

* * *

Auf dieser Seite

Einführung Einführung
CopyMemory CopyMemory
CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW
SetSecurityDescriptorDacl SetSecurityDescriptorDacl
Funktionen für den Identitätswechsel Funktionen für den Identitätswechsel
memcpy memcpy
sprintf, swprintf sprintf, swprintf
strcat, wcscat, _mbscat strcat, wcscat, _mbscat
strcpy, wcscpy, _mbscpy strcpy, wcscpy, _mbscpy
strncat, wcsncat, _mbsncat strncat, wcsncat, _mbsncat
WinExec WinExec
Schlussfolgerung Schlussfolgerung

Einführung

Als ich C- und C++-Code auf Sicherheitsschwächen hin überprüfte, stieß ich auf einige häufige Fehler bei der Verwendung bestimmter Funktionsaufrufe. Obwohl sich ein Funktionsaufruf ggf. nicht auf die Sicherheit bezieht, zieht er bei falscher Verwendung u.U. versteckte Sicherheitsänderungen nach sich.

In diesem Artikel werden diese Fehler und die entsprechenden Sicherheitsänderungen sowie die empfohlene Verwendung bestimmter Funktionen beschrieben.

Wir haben in einigen in MSDN und im Plattform-SDK dokumentierten APIs bereits Sicherheitsthemen erläutert (dies ist ein fortlaufender Prozess). In den ersten Diskussionen wurden die wichtigsten Funktionsaufrufe beschrieben, die sowohl in Microsoft- als auch in anderen Produkten zu Sicherheitsschwächen geführt haben.

 

CopyMemory

Sicherheitsanmerkungen

Das erste Argument, Destination, muss alle count-Bytes von Source enthalten können,da ansonsten der Pufferspeicher "überlaufen" könnte. Dabei könnte es bei einer Zugriffsverletzung zu Denial-of-Service-Angriffen auf die Anwendung kommen. Im schlimmsten Fall wird zugelassen, dass ein Angreifer ausführbaren Code in Ihren Prozess einfügt. Dies ist v.a. dann möglich, falls es sich bei Destination um einen stapelbasierten Pufferspeicher handelt. Beachten Sie, dass das letzte Argument, Length, die Anzahl der in Destination zu kopierenden Bytes ist, nicht die Größe von Destination.

Im folgenden Codebeispiel wird eine sichere Verwendungsmethode für CopyMemory() gezeigt:

void test(char *pbData, unsigned int cbData) { 
 char buf[BUFFER_SIZE]; 
 CopyMemory(buf, pbData, min(cbData,BUFFER_SIZE)); 
} 

 

CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW

Sicherheitsanmerkungen
Der erste Parameter, lpApplicationName, kann NULL sein. In diesem Fall muss der Name der ausführbaren Datei die erste durch Leerzeichen begrenzte Zeichenfolge in lpCommandLine sein. Wenn der Name der ausführbaren Datei oder des Pfads jedoch ein Leerzeichen enthält, riskieren Sie, dass eine aggressive ausführbare Datei ausgeführt wird, wenn die Leerzeichen nicht korrekt gesetzt werden. Das folgende Beispiel gefährdet die Sicherheit der Anwendung, weil der Prozess versucht, anstelle von "foo.exe" die Datei "Program.exe" auszuführen, falls sie vorhanden ist.

CreateProcess(NULL, "C:\Programme\foo", ...)

Wenn ein böswilliger Benutzer in einem System ein trojanisches Programm mit dem Namen "Program.exe" erstellt, starten alle Programme, die CreateProcess über das Verzeichnis Programme nicht richtig aufrufen, jetzt anstatt der beabsichtigten Anwendung das trojanische Programm.

Achten Sie darauf, für lpApplicationName nicht NULL zu übergeben, um so zu verhindern, dass die Funktion den Pfadnamen der ausführbaren Datei aus den Runtime-Parametern analysieren und ermitteln muss. Setzen Sie den Pfad der ausführbaren Datei in lpCommandLine ansonsten in Anführungszeichen, wenn lpApplicationName NULL ist, wie im Beispiel unten gezeigt.

CreateProcess(NULL, "\"C:\Programme\foo.exe\" -L -S", ...) 

 

SetSecurityDescriptorDacl

Sicherheitsanmerkungen
Wir raten unbedingt vom Erstellen von Sicherheitsdeskriptoren mit NULL DACL (d.h. pDacl ist NULL) ab. Eine solche DACL, gewährleistet für das Objekt keine Sicherheit. Ein Hacker könnte in diesem Fall für das Objekt einen "Everyone"-ACE (Deny All Access) einrichten, so dass allen Benutzern, auch Administratoren, der Zugriff auf das Objekt verweigert wird. Eine NULL DACL bietet keinen Schutz gegen einen solchen Angriff.

 

Funktionen für den Identitätswechsel

Sicherheitsanmerkungen
Wenn die Aufrufe einer Funktion für den Identitätswechsel aus irgendeinem Grund fehlschlagen, wird die Identität des Clients nicht angenommen, und die Clientanforderung wird in dem Sicherheitskontext des Prozesses gestellt, aus dem der Aufruf gemacht wurde. Wenn der Prozess ein hochprivilegiertes Konto ausführt, z.B. als LocalSystem oder als Mitglied einer Verwaltungsgruppe, kann der Benutzer ggf. Aktionen ausführen, die ansonsten nicht zulässig wären. Daher ist es wichtig, dass Sie immer den Rückgabewert des Aufrufs prüfen. Wenn er keinen Fehler anzeigt, setzen Sie die Clientanforderung nicht weiter fort. Beispiele:
RpcImpersonateClient
ImpersonateNamedPipeClient
SetThreatToken
ImpersonateSelf
CoImpersonateClient
ImpersonateDdeClientWindow
ImpersonateSecurityContext
ImpersonateLoggedOnUser

 

memcpy

Sicherheitsanmerkungen
Das erste Argument, dest, muss alle count-Bytes von src enthalten können,da ansonsten der Pufferspeicher "überlaufen" könnte. Dabei könnte es bei einer Zugriffsverletzung zu Denial-of-Service-Angriffen auf die Anwendung kommen. Im schlimmsten Fall wird zugelassen, dass ein Angreifer ausführbaren Code in Ihren Prozess einfügt. Dies ist v.a. dann möglich, wenn es sich bei dest um einen stapelbasierten Pufferspeicher handelt. Beachten Sie, dass das letzte Argument, count, die Anzahl der in dest zu kopierenden Bytes ist, nicht die Größe von dest.

Im folgenden Codebeispiel wird eine sichere Methode für die Verwendung von memcpy() gezeigt:

void test(char *pbData, unsigned int cbData) { 
 char buf[BUFFER_SIZE]; 
 memcpy(buf, pbData, min(cbData,BUFFER_SIZE)); 
} 

 

sprintf, swprintf

Sicherheitsanmerkungen
Das erste Argument, buffer, muss die formatierte Version von format und das nachfolgende NULL-Zeichen ('\0') enthalten können,da ansonsten der Pufferspeicher "überlaufen" könnte. Dabei könnte es bei einer Zugriffsverletzung zu Denial-of-Service-Angriffen auf die Anwendung kommen. Im schlimmsten Fall wird zugelassen, dass ein Angreifer ausführbaren Code in Ihren Prozess einfügt. Dies ist v.a. dann möglich, wenn es sich bei buffer um einen stapelbasierten Pufferspeicher handelt.

Beachten Sie außerdem die Gefahren, wenn ein Benutzer oder eine Anwendung format als Variable angibt. Das folgende Beispiel gefährdet die Sicherheit der Anwendung, weil ein Angreifer szTemplate auf "%90s%10s" setzen kann, wodurch eine 100-Byte-Zeichenfolge entsteht:

void test(char *szTemplate,char *szData1, char *szData2) { 
 char buf[BUFFER_SIZE]; 
 sprintf(buf,szTemplate,szData1,szData2); 
} 

Verwenden Sie stattdessen ggf. _snprintf oder _snwprintf.

 

strcat, wcscat, _mbscat

Sicherheitsanmerkungen
Das erste Argument, strDestination, muss die aktuelle strDestination von strSourceund eine schließende '\0' enthalten können, da ansonsten der Pufferspeicher "überlaufen" könnte. Dabei könnte es bei einer Zugriffsverletzung zu Denial-of-Service-Angriffen auf die Anwendung kommen. Im schlimmsten Fall wird zugelassen, dass ein Angreifer ausführbaren Code in Ihren Prozess einfügt. Dies ist v.a. dann möglich, wenn es sich bei strDestination um einen stapelbasierten Pufferspeicher handelt. Verwenden Sie ggf. strncat, wcsncat oder _mbsncat.

 

strcpy, wcscpy, _mbscpy

Sicherheitsanmerkungen
Das erste Argument, strDestination, muss strSource und eine schließende '\0' enthalten können, da ansonsten der Pufferspeicher "überlaufen" könnte. Dabei könnte es bei einer Zugriffsverletzung zu Denial-of-Service-Angriffen auf die Anwendung kommen. Im schlimmsten Fall wird zugelassen, dass ein Angreifer ausführbaren Code in Ihren Prozess einfügt. Dies ist v.a. dann möglich, wenn es sich bei strDestination um einen stapelbasierten Pufferspeicher handelt. Verwenden Sie ggf. strncpy, wcsncpy, or _mbsncpy.

 

strncat, wcsncat, _mbsncat

Sicherheitsanmerkungen
Das erste Argument, strDestination, muss die aktuelle strDestination von strSource und eine schließende NULL ('\0') enthalten können, da ansonsten der Pufferspeicher "überlaufen" könnte. Dabei könnte es bei einer Zugriffsverletzung zu Denial-of-Service-Angriffen auf die Anwendung kommen. Im schlimmsten Fall wird zugelassen, dass ein Angreifer ausführbaren Code in Ihren Prozess einfügt. Dies ist v.a. dann möglich, wenn es sich bei strDestination um einen stapelbasierten Pufferspeicher handelt. Beachten Sie, dass das letzte Argument, count, die Anzahl der in strDestination zu kopierenden Bytes ist, nicht die Größe von strDestination.

Beachten Sie außerdem, dass strncat nur dann eine nachfolgende NULL hinzufügt, wenn im Pufferspeicher, strDestination, Platz dafür bleibt.

Im folgenden Beispiel wird eine sichere Methode für die Verwendung von strncat gezeigt:

void test(char *szWords1, char *szWords2) { 
  char buf[BUFFER_SIZE]; 
  strncpy(buf,szWords1,sizeof buf - 1); 
  buf[BUFFER_SIZE - 1] = '\0';  
  unsigned int cRemaining = (sizeof buf - strlen(buf)) - 1; 
  strncat(buf,szWords2,cRemaining); 
} 

 

WinExec

Sicherheitsanmerkungen
Der Name der ausführbaren Datei wird wie die erste durch Leerzeichen begrenzte Zeichenfolge in lpCmdLine verarbeitet. Wenn der Name der ausführbaren Datei oder des Pfads jedoch ein Leerzeichen enthält, riskieren Sie, dass eine aggressive ausführbare Datei ausgeführt wird, wenn die Leerzeichen nicht korrekt gesetzt werden. Das folgende Beispiel gefährdet die Sicherheit der Anwendung, weil der Prozess versucht, anstelle von "foo.exe" die Datei "Program.exe" auszuführen, falls sie vorhanden ist.

WinExec("C:\Programme\foo", ...)

Wenn ein böswilliger Benutzer in einem System ein trojanisches Programm mit dem Namen "Program.exe" erstellt, starten alle Programme, die WinExec über das Verzeichnis Programme nicht richtig aufrufen, jetzt anstatt der beabsichtigten Anwendung das trojanische Programm.

Vom Sicherheitsstandpunkt aus wird empfohlen, CreateProcess anstelle von WinExec zu verwenden. Wenn Sie WinExec aus Gründen der Legacy verwenden, stellen Sie sicher, dass der Anwendungsname durch Anführungszeichen eingebunden ist, wie im folgenden Beispiel gezeigt:

WinExec("\"C:\Programme\foo.exe\" -L -S", ...) 

 

Schlussfolgerung

Im Lauf der Zeit werden wir die Liste erweitern, wenn wir mehr über falsche Verwendungen der Funktionen erfahren. Wenn Sie Kommentare zu diesen Funktionsaufrufen haben, schreiben Sie mir unter mikehow@microsoft.com.