it-swarm.com.de

Warum ist dieser Code anfällig für Pufferüberlaufangriffe?

int func(char* str)
{
   char buffer[100];
   unsigned short len = strlen(str);

   if(len >= 100)
   {
        return (-1);
   }

   strncpy(buffer,str,strlen(str));
   return 0;
}

Dieser Code ist anfällig für einen Pufferüberlauf und ich versuche herauszufinden, warum. Ich denke, es hat damit zu tun, dass len anstelle eines short als int deklariert wird, aber ich bin mir nicht sicher.

Irgendwelche Ideen?

148
Jason

Bei den meisten Compilern beträgt der Maximalwert von unsigned short 65535.

Jeder darüber liegende Wert wird umbrochen, sodass 65536 zu 0 und 65600 zu 65 wird.

Dies bedeutet, dass lange Zeichenfolgen mit der richtigen Länge (z. B. 65600) die Prüfung bestehen und den Puffer überlaufen.


Verwenden Sie size_t, Um das Ergebnis von strlen() und nicht von unsigned short Zu speichern, und vergleichen Sie len mit einem Ausdruck, der die Größe von buffer. Also zum Beispiel:

char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0]))  return -1;
memcpy(buffer, str, len + 1);
193
orlp

Das Problem ist hier:

strncpy(buffer,str,strlen(str));
                   ^^^^^^^^^^^

Wenn die Zeichenfolge länger als der Zielpuffer ist, kopiert strncpy sie trotzdem. Sie basieren auf der Anzahl der Zeichen der Zeichenfolge als zu kopierende Zahl anstelle der Größe des Puffers. Der richtige Weg, dies zu tun, ist wie folgt:

strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = '\0';

Dies begrenzt die Datenmenge, die auf die tatsächliche Größe des Puffers minus eins für das Null-Abschlusszeichen kopiert wird. Dann setzen wir das letzte Byte im Puffer als zusätzlichen Schutz auf das Null-Zeichen. Der Grund dafür ist, dass strncpy bis zu n Bytes einschließlich der abschließenden Null kopiert, wenn strlen (str) <len - 1. Wenn nicht, wird die Null nicht kopiert, und Sie haben ein Absturzszenario, da Ihr Puffer jetzt nicht abgeschlossen ist Zeichenfolge.

Hoffe das hilft.

BEARBEITEN: Nach weiterer Prüfung und Eingabe von anderen ergibt sich eine mögliche Kodierung für die Funktion:

int func (char *str)
  {
    char buffer[100];
    unsigned short size = sizeof(buffer);
    unsigned short len = strlen(str);

    if (len > size - 1) return(-1);
    memcpy(buffer, str, len + 1);
    buffer[size - 1] = '\0';
    return(0);
  }

Da wir die Länge der Zeichenfolge bereits kennen, können wir memcpy verwenden, um die Zeichenfolge von der Position, auf die str verweist, in den Puffer zu kopieren. Beachten Sie, dass auf der Handbuchseite für strlen (3) (auf einem FreeBSD 9.3-System) Folgendes angegeben ist:

 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.

Was ich dahingehend interpretiere, dass die Länge der Zeichenkette nicht die Null enthält. Aus diesem Grund kopiere ich len + 1 Byte, um die Null einzuschließen, und der Test überprüft, ob die Länge <Größe des Puffers - 2 ist. Abzüglich eins, da der Puffer an Position 0 beginnt, und minus eins, um sicherzustellen, dass Platz vorhanden ist für die Null.

BEARBEITEN: Es stellt sich heraus, dass die Größe von etwas mit 1 beginnt, während der Zugriff mit 0 beginnt. Daher war die vorherige -2 falsch, da ein Fehler für> 98 Byte zurückgegeben wurde, der jedoch> 99 Byte betragen sollte.

BEARBEITEN: Obwohl die Antwort zu einem nicht signierten Short im Allgemeinen richtig ist, da die maximale Länge, die dargestellt werden kann, 65.535 Zeichen beträgt, spielt es keine Rolle, ob der String länger ist oder nicht. Es ist so, als würde man 75.231 (das ist 0x000125DF) nehmen und die oberen 16 Bits maskieren, was 9695 (0x000025DF) ergibt. Das einzige Problem, das ich dabei sehe, sind die ersten 100 Zeichen nach 65.535, da die Längenprüfung das Kopieren zulässt, es werden jedoch in allen Fällen nur die ersten 100 Zeichen der Zeichenfolge kopiert und die Zeichenfolge mit Null beendet. Auch bei diesem Problem wird der Puffer nicht überlaufen.

Dies kann an sich ein Sicherheitsrisiko darstellen, abhängig vom Inhalt der Zeichenfolge und dem Zweck, für den Sie sie verwenden. Wenn es sich nur um geraden Text handelt, der von Menschen gelesen werden kann, gibt es im Allgemeinen kein Problem. Sie erhalten nur eine abgeschnittene Zeichenfolge. Wenn es sich jedoch um eine URL oder gar eine SQL-Befehlsfolge handelt, besteht möglicherweise ein Problem.

28
Daniel Rudy

Auch wenn Sie strncpy verwenden, hängt die Länge des Cutoffs vom übergebenen String-Zeiger ab. Sie haben keine Ahnung, wie lang diese Zeichenfolge ist (dh die Position des Nullterminators relativ zum Zeiger). Wenn Sie also nur strlen aufrufen, sind Sie anfällig für Sicherheitslücken. Wenn Sie sicherer sein möchten, verwenden Sie strnlen(str, 100).

Vollständiger Code korrigiert wäre:

int func(char *str) {
   char buffer[100];
   unsigned short len = strnlen(str, 100); // sizeof buffer

   if (len >= 100) {
     return -1;
   }

   strcpy(buffer, str); // this is safe since null terminator is less than 100th index
   return 0;
}
11
Patrick Roberts

Die Antwort mit der Verpackung ist richtig. Aber es gibt ein Problem, von dem ich denke, dass es nicht erwähnt wurde, wenn (len> = 100)

Nun, wenn Len 100 wäre, würden wir 100 Elemente kopieren und wir hätten kein abschließendes\0. Das würde eindeutig bedeuten, dass jede andere Funktion, die von der richtigen Endung der Zeichenfolge abhängt, weit über das ursprüngliche Array hinausgeht.

Das Stringproblem von C ist meiner Meinung nach unlösbar. Du solltest immer ein paar Limits vor dem Anruf haben, aber selbst das wird nicht helfen. Es gibt keine Grenzen zu überprüfen und so können und werden Pufferüberläufe immer passieren ....

4
Friedrich

Abgesehen von den Sicherheitsproblemen, die beim mehrmaligen Aufrufen von strlen auftreten, sollten generell keine Zeichenfolgenmethoden für Zeichenfolgen verwendet werden, deren Länge genau bekannt ist. Für die meisten Zeichenfolgenfunktionen gibt es nur einen sehr engen Fall, in dem sie verwendet werden sollten. auf Saiten, für die eine maximale Länge garantiert werden kann, deren genaue Länge jedoch nicht bekannt ist]. Sobald die Länge der Eingabezeichenfolge und die Länge des Ausgabepuffers bekannt sind, sollte man herausfinden, wie groß eine Region kopiert werden soll, und dann memcpy() verwenden, um die fragliche Kopie tatsächlich auszuführen. Obwohl es möglich ist, dass strcpy die Leistung von memcpy() übertrifft, wenn eine Zeichenfolge von etwa 1-3 Byte kopiert wird, ist es auf vielen Plattformen wahrscheinlich, dass memcpy() mehr als doppelt so groß ist schnell im Umgang mit größeren Saiten.

Es gibt zwar Situationen, in denen die Sicherheit auf Kosten der Leistung gehen würde, in denen jedoch der sichere Ansatz auch schneller ist. In einigen Fällen kann es sinnvoll sein, Code zu schreiben, der nicht gegen verrückte Eingaben geschützt ist, wenn der Code, der die Eingaben bereitstellt, ein gutes Verhalten sicherstellen kann und die Leistung durch Schutz vor missbräuchlichen Eingaben beeinträchtigt wird. Das Sicherstellen, dass Zeichenfolgenlängen nur einmal überprüft werden, verbessert beides die Leistung und die Sicherheit. Es kann jedoch eine zusätzliche Maßnahme ergriffen werden, um die Sicherheit zu gewährleisten, auch wenn die Zeichenfolgenlänge manuell verfolgt wird: für jede Zeichenfolge, für die eine nachgestellte Null erwartet wird Schreiben Sie die nachgestellte Null explizit, anstatt zu erwarten, dass die Quellzeichenfolge sie enthält. Wenn man also ein strdup Äquivalent schreibt:

char *strdupe(char const *src)
{
  size_t len = strlen(src);
  char *dest = malloc(len+1);
  // Calculation can't wrap if string is in valid-size memory block
  if (!dest) return (OUT_OF_MEMORY(),(char*)0); 
  // OUT_OF_MEMORY is expected to halt; the return guards if it doesn't
  memcpy(dest, src, len);      
  dest[len]=0;
  return dest;
}

Beachten Sie, dass die letzte Anweisung im Allgemeinen weggelassen werden könnte, wenn memcpy len+1 - Bytes verarbeitet hätte. Wenn jedoch ein anderer Thread die Quellzeichenfolge ändern würde, könnte das Ergebnis eine nicht mit NUL terminierte Zielzeichenfolge sein.

3
supercat