2010-09-07 12 views
17

OWASP sagt:Verständnis der Gefahren von sprintf (...)

„C-Bibliotheksfunktionen wie strcpy (), strcat(), sprintf() und vsprintf () arbeiten auf null Strings beendet und führen Sie keine Beschränkungen durch. "

sprintf schreibt Daten auf Zeichenfolge int sprintf (char * str, const char * Format, ...) formatiert;

Beispiel:

sprintf(str, "%s", message); // assume declaration and 
          // initialization of variables 

Wenn ich OWASP Kommentar verstehen, dann sind die Gefahren sprintf der Verwendung sind, dass

1), wenn Nachricht 's length>str' s Länge, eine ist Pufferüberlauf

und

2) wenn Nachricht nicht null-enden mit \0, dann Nachricht in str über die Speicheradresse Nachricht kopiert werden kann, was zu einem Pufferüberlauf

Bitte bestätigen/verneinen. Danke

+2

Auch Programmierer Fehler wie 'sprintf (str, Nachricht)' oder ähnliches ist ein echtes Risiko. – You

Antwort

20

Sie sind auf beiden Problemen richtig, obwohl sie wirklich beide das gleiche Problem sind (das auf Daten über die Grenzen eines Arrays hinaus zugreift). Eine Lösung für Ihr erstes Problem ist die Verwendung snprintf, die eine Puffergröße als Argument akzeptiert.

Eine Lösung für Ihr zweites Problem ist eine maximale Länge Argument s[n]printf geben. Zum Beispiel:

char buffer[128]; 

snprintf(buffer, sizeof(buffer), "This is a %.4s\n", "testGARBAGE DATA"); 

// strcmp(buffer, "This is a test\n") == 0 

Wenn Sie die gesamte Zeichenfolge gespeichert werden sollen (zB im Fall sizeof(buffer) zu klein ist), laufen snprintf zweimal:

// Behaviour is different in SUSv2; see 
// "conforming to" section of man 3 sprintf 

int length = snprintf(NULL, 0, "This is a %.4s\n", "testGARBAGE DATA"); 

++length;   // +1 for null terminator 
char *buffer = malloc(length); 

snprintf(buffer, length, "This is a %.4s\n", "testGARBAGE DATA"); 

(Sie wahrscheinlich diese in eine Funktion passen können mit va.)

+4

Wie sind sie "beide das gleiche Problem", wenn "snprintf" nur die erste löst? –

+0

@Rob Kennedy, Sie sind das gleiche Problem an verschiedenen Orten. Entschuldigung, das war nicht klar. Du hast recht; 'snprintf', der' sprintf' blind ersetzt, behebt nur den ersten. – strager

+0

snprintf schneidet auch die übergebenen Argumente ab und löst die Nummer 2 (wenn die Nachricht nicht NULL-terminiert ist, werden nur die ersten N Zeichen geschrieben). –

1

Ihre Interpretation scheint korrekt zu sein. Ihr Fall Nr. 2 ist jedoch kein Pufferüberlauf. Es ist eher eine Speicherzugriffsverletzung. Das ist nur eine Terminologie, es ist immer noch ein großes Problem.

4

Ja, meistens handelt es sich um Pufferüberläufe. Dies sind jedoch heutzutage sehr ernstzunehmende Geschäftsvorgänge, da Pufferüberläufe der Hauptangriffsvektor sind, der von Systemcrackern verwendet wird, um Software- oder Systemsicherheit zu umgehen. Wenn Sie so etwas den Benutzereingaben aussetzen, besteht eine sehr gute Chance, dass Sie die Schlüssel an Ihr Programm (oder sogar Ihren Computer selbst) den Crackern übergeben.

Aus der Perspektive von OWASP, täuschen wir vor, dass wir einen Webserver schreiben, und wir verwenden sprintf, um die Eingaben zu analysieren, die ein Browser uns übergibt.

Nehmen wir jetzt an, jemand böswilliger da draußen übergibt unseren Webbrowser eine Zeichenfolge weit größer als in den von uns ausgewählten Puffer passt. Seine zusätzlichen Daten überschreiben stattdessen Daten in der Nähe. Wenn er es groß genug macht, werden einige seiner Daten über die Anweisungen des Webservers und nicht über seine Daten kopiert. Jetzt kann er unseren Webserver veranlassen seinen Code auszuführen.

8

Beide Behauptungen sind korrekt.

Es gibt ein zusätzliches Problem, das nicht erwähnt wird. Es gibt keine Typprüfung der Parameter. Wenn Sie die Formatzeichenfolge und die Parameter nicht übereinstimmen, kann dies zu undefiniertem und unerwünschtem Verhalten führen. Zum Beispiel:

char buf[1024] = {0}; 
float f = 42.0f; 
sprintf(buf, "%s", f); // `f` isn't a string. the sun may explode here 

Dies kann besonders unangenehm sein, um zu debuggen.

Alle oben genannten führen viele C++ - Entwickler zu dem Schluss, dass Sie nie sprintf und seine Brüder verwenden sollten. In der Tat gibt es Einrichtungen, die Sie verwenden können, um alle oben genannten Probleme zu vermeiden. Eine, ströme, befindet sich direkt in der Sprache gebaut:

#include <sstream> 
#include <string> 

// ... 

float f = 42.0f; 

stringstream ss; 
ss << f; 
string s = ss.str(); 

... und eine andere beliebte Wahl für diejenigen, die, wie ich, immer noch lieber sprintf verwenden kommt aus dem boost Format libraries:

#include <string> 
#include <boost\format.hpp> 

// ... 

float f = 42.0f; 
string s = (boost::format("%1%") %f).str(); 

Solltest du das Mantra "Nimm Sprint" anwenden? Entscheide dich selbst. Es gibt normalerweise das beste Werkzeug für den Job und je nachdem, was Sie tun, sprintf könnte es nur sein.

+1

GCC (und ich glaube, viele andere) führen Soft-Typ-Checking durch. Außerdem ist das Pfadtrennzeichen in '# include''/'egal welche Plattform. – Potatoswatter

+0

@Potatoswatter: Die Interpretation eines beliebigen Zeichens (einschließlich '\' und '/') in '# include' wird der Implementierung überlassen. Der Standard behauptet nicht einmal, dass es ein Dateisystem gibt, geschweige denn Pfade mit Pfadtrennzeichen. – MSalters

+0

@MSalters: Ja, aber im Grunde unterstützt jede Implementierung, die Pfadnamen unterstützt, '/' für Kompatibilität mit existierendem Code und Lehrbüchern und nicht annähernd so sehr mit '\'. Vielleicht noch wichtiger ist, dass das Vorhandensein von '\' einen universellen Charakternamen hervorbringen kann. Aus diesem Grund kann eine Plattform sehr vernünftig entscheiden, Ersatzsequenzen anstelle von oder zusätzlich zur Verwendung als Trennzeichen zu verwenden. Also, was ist der Vorteil? – Potatoswatter

1

Die sprintf-Funktion, wenn sie mit bestimmten Formatbezeichnern verwendet wird, birgt zwei Arten von Sicherheitsrisiken: (1) Schreibspeicher sollte es nicht; (2) Lesespeicher sollte es nicht. Wenn snprintf mit einem Size-Parameter verwendet wird, der dem Puffer entspricht, schreibt er nichts, was nicht der Fall sein sollte. Abhängig von den Parametern kann es immer noch Dinge lesen, die es nicht tun sollte. Abhängig von der Betriebsumgebung und was sonst noch ein Programm tut, kann die Gefahr von unkorrekten Lesevorgängen weniger schwerwiegend sein als die von unkorrekten Schreibvorgängen.

2

Ihre 2 nummerierten Schlussfolgerungen sind korrekt, aber unvollständig.

Es gibt ein zusätzliches Risiko:

char* format = 0; 
char buf[128]; 
sprintf(buf, format, "hello"); 

Hier format nicht NULL-terminiert. sprintf() überprüft das auch nicht.

0

Es ist sehr wichtig, daran zu denken, dass sprintf() das Zeichen ASCII 0 als String-Terminator am Ende jeder Zeichenfolge hinzufügt. Daher muss der Zielpuffer mindestens n + 1 Bytes haben (Um das Wort "HELLO" zu drucken, ist ein 6-Byte-Puffer erforderlich, NICHT 5)

Im folgenden Beispiel ist es möglicherweise nicht offensichtlich, aber in der 2-Byte-Zielpuffer, wird das zweite Byte durch ASCII-Zeichen 0 überschrieben. Wenn nur 1 Byte für den Puffer reserviert wäre, würde dies einen Pufferüberlauf verursachen.

char buf[3] = {'1', '2'}; 
int n = sprintf(buf, "A"); 

Beachten Sie auch, dass der Rückgabewert von sprintf() NICHT das nullendezeichen enthält. Im obigen Beispiel wurden 2 Bytes geschrieben, aber die Funktion gibt '1' zurück.

Im folgenden Beispiel wird das erste Byte der Klassenmembervariablen 'i' teilweise von sprintf() überschrieben (auf einem 32-Bit-System).

struct S 
{ 
    char buf[4]; 
    int i; 
}; 


int main() 
{ 
    struct S s = { }; 
    s.i = 12345; 

    int num = sprintf(s.buf, "ABCD"); 
    // The value of s.i is NOT 12345 anymore ! 

    return 0; 
}