Bekämpfung von Pufferüberläufen und anderen Speicherverwaltungsfehlern

Kopfschuss von Arthur Hicken, Parasoft-Evangelist

Von Arthur Hicken

3. September 2020

9  min lesen

Wenn die Datenmenge die Speicherkapazität des Speicherpuffers überschreitet, kommt es zu einem Pufferüberlauf. Sehen Sie sich an, wie die Static Analysis Checker-Funktion in der Parasoft C/C++-Lösung Ihnen helfen kann, Fehler aufgrund von Pufferüberläufen zu verwalten.

Die Speicherverwaltung ist insbesondere in C und C ++ mit Gefahren behaftet. Tatsächlich machen Fehler, die mit Speicherverwaltungsschwächen verbunden sind, einen beträchtlichen Teil der CWE Top 25 aus. Acht der Top 25 stehen in direktem Zusammenhang mit Pufferüberläufen, schlechten Zeigern und Speicherverwaltung.

Die mit Abstand größte Softwareschwäche ist CWE-119, „Unsachgemäße Beschränkung von Operationen innerhalb der Grenzen eines Speicherpuffers“. Diese Arten von Fehlern spielen eine herausragende Rolle bei Sicherheitsproblemen in allen Arten von Software, einschließlich sicherheitskritischer Anwendungen in Automobilen, medizinischen Geräten und Avionik.

Hier sind die Speicherfehler im Zusammenhang mit allgemeinen Schwachstellenaufzählungen aus den CWE Top 25:

RangIDNameScore  
[1]CWE-119Unsachgemäße Einschränkung von Operationen innerhalb der Grenzen eines Speicherpuffers75.56
[5]CWE-125Out-of-bounds lesen26.53
[7]CWE-416Verwenden Sie nach Frei17.94
[8]CWE-190Integer Overflow oder Wraparound17.35
[12]CWE-787Außerhalb der Grenzen schreiben11.08
[14]CWE-476NULL-Zeiger-Dereferenzierung9.74
[20]CWE-400Unkontrollierter Ressourcenverbrauch5.04
[21]CWE-772Fehlende Ressourcenfreigabe nach effektiver Lebensdauer Life5.04

Obwohl diese Fehler C, C ++ und andere Sprachen seit Jahrzehnten plagen, treten sie heute immer häufiger auf. Sie sind gefährliche Fehler in Bezug auf Konsequenzen für Qualität, Sicherheit und Zuverlässigkeit, und ihre Anwesenheit ist eine der Hauptursachen für Sicherheitslücken.

Hauptursache für Sicherheitslücken

Microsoft entdeckt In den letzten 12 Jahren waren über 70% der Sicherheitslücken in ihren Produkten auf Probleme mit der Speichersicherheit zurückzuführen. Diese Arten von Fehlern sind die größte Angriffsfläche für ihre Anwendung und werden von Hackern verwendet. Ihren Untersuchungen zufolge waren die Hauptursachen für Sicherheitsangriffe die unzulässige, nicht nachträgliche und nicht initialisierte Nutzung. Wie sie hervorheben, gibt es die Schwachstellenklassen seit 20 Jahren oder länger und sie sind bis heute weit verbreitet.

In ähnlicher Weise stellte Google fest, dass 70% der Sicherheitslücken im Chromium-Projekt (der Open Source-Basis für den Chrome-Browser) auf dieselben Speicherverwaltungsprobleme zurückzuführen sind. Ihre Hauptursache war auch die nachträgliche Nutzung, wobei andere unsichere Speicherverwaltungen an zweiter Stelle standen.

Angesichts dieser Beispiele realer Erkenntnisse ist es wichtig, dass Softwareteams diese Art von Fehlern ernst nehmen. Glücklicherweise gibt es Möglichkeiten, diese Art von Problemen mit einer effektiven und effizienten statischen Analyse zu verhindern und zu erkennen.

Wie Speicherverwaltungsfehler zu Sicherheitslücken führen

In den meisten Fällen sind Speicherverwaltungsfehler das Ergebnis schlechter Programmierpraktiken bei Verwendung von Zeigern in C / C ++ und direktem Zugriff auf den Speicher. In anderen Fällen hängt es damit zusammen, dass schlechte Annahmen über die Länge und den Inhalt von Daten getroffen werden.

Diese Software-Schwachstellen werden am häufigsten mit fehlerhaften Daten ausgenutzt, Daten von außerhalb der Anwendung, die nicht auf Länge oder Format überprüft wurden. Das berüchtigte Heartbleed Bei einer Sicherheitsanfälligkeit wird ein Pufferüberlauf ausgenutzt. Technisch gesehen ist es ein Puffer überlesen. Wie wir in unserem vorherigen Blog besprochen haben SQL-InjektionenDie Verwendung von Eingaben, die nicht überprüft und nicht eingeschränkt sind, ist ein Sicherheitsrisiko.

Betrachten wir einige der Hauptkategorien von Schwachstellen in der Speicherverwaltungssoftware. Das übergeordnete ist CWE-119: Unsachgemäße Einschränkung von Operationen innerhalb der Grenzen eines Speicherpuffers.

Pufferüberlauf

Programmiersprachen (meistens C und C ++), die einen direkten Zugriff auf den Speicher ermöglichen und nicht automatisch überprüfen, auf welche Speicherorte zugegriffen wird, sind gültig und anfällig für Speicherbeschädigungsfehler. Diese Beschädigung kann in Daten- und Codebereichen des Speichers auftreten, die vertrauliche Informationen offenlegen, zu einer unbeabsichtigten Codeausführung führen oder zum Absturz einer Anwendung führen können.

Das folgende Beispiel zeigt einen klassischen Fall eines Pufferüberlaufs von CWE-120:

char last_name [20]; printf ("Geben Sie Ihren Nachnamen ein:"); scanf ("% s", Nachname);

In diesem Fall gibt es keine Einschränkung für die Benutzereingabe von scanf () noch die Grenze für die Länge von Familienname, Nachname ist 20 Zeichen. Wenn Sie einen Nachnamen mit mehr als 20 Zeichen eingeben, wird die Benutzereingabe über die Grenzen des Puffers hinaus in den Speicher kopiert Familienname, Nachname. Hier ist ein subtileres Beispiel aus CWE-119:

void host_lookup (char * user_supplied_addr) {struct hostent * hp; in_addr_t * addr; char Hostname [64]; in_addr_t inet_addr (const char * cp); / * Routine, die sicherstellt, dass user_supplied_addr das richtige Format für die Konvertierung hat * / validate_addr_form (user_supplied_addr); addr = inet_addr (user_supplied_addr); hp = gethostbyaddr (addr, sizeof (struct in_addr), AF_INET); strcpy (Hostname, hp-> h_name); }}

Diese Funktion verwendet eine vom Benutzer angegebene Zeichenfolge mit einer IP-Adresse (z. B. 127.0.0.1) und ruft den Hostnamen dafür ab.

Die Funktion validiert die Benutzereingabe (gut!), Überprüft jedoch nicht die Ausgabe von gethostbyaddr ()(Schlecht!). In diesem Fall reicht ein langer Hostname aus, um den derzeit auf 64 Zeichen beschränkten Hostnamenpuffer zu überlaufen. Beachten Sie, dass wenn gethostaddr () Gibt eine Null zurück, wenn ein Hostname nicht gefunden werden kann. Es gibt auch einen Nullzeiger-Dereferenzierungsfehler!

Use-After-Free-Fehler

Interessanterweise stellte Microsoft in seiner Studie fest, dass Use-After-Free-Fehler die häufigsten Probleme bei der Speicherverwaltung waren. Wie der Name schon sagt, bezieht sich der Fehler auf die Verwendung von Zeigern (im Fall von C / C ++), die auf zuvor freigegebenen Speicher zugreifen. C und C ++ verlassen sich normalerweise auf den Entwickler, um die Speicherzuordnung zu verwalten, was oft schwierig ist, wenn es vollständig korrekt ausgeführt wird. Wie das folgende Beispiel (aus CWE-416) zeigt, ist es oft einfach anzunehmen, dass ein Zeiger noch gültig ist:

char * ptr = (char *) malloc (GRÖSSE); if (err) {abrt = 1; frei (ptr); } ... if (abrt) {logError ("Operation vor Festschreiben abgebrochen", ptr); }}

Im obigen Beispiel der Zeiger ptr ist frei, wenn ein Fehler wahr ist, wird dann aber später nach der Freigabe dereferenziert, wenn abr ist wahr (was auf wahr gesetzt ist, wenn sich irren ist wahr). Dies mag erfunden erscheinen, aber wenn sich zwischen diesen beiden Codefragmenten viel Code befindet, ist dies leicht zu übersehen. Darüber hinaus kann dies nur in einem Fehlerzustand auftreten, der nicht ordnungsgemäß getestet wird.

NULL-Zeiger-Dereferenzierung

Eine weitere häufige Schwachstelle in der Software ist die Verwendung von Zeigern (oder Objekten in C ++ und Java), von denen erwartet wird, dass sie gültig sind, aber NULL sind. Obwohl diese Dereferenzen in Sprachen wie Java als Ausnahmen abgefangen werden, können sie dazu führen, dass eine Anwendung angehalten, beendet oder abgestürzt wird. Nehmen Sie das folgende Beispiel in Java von CWE-476:

String cmd = System.getProperty ("cmd"); cmd = cmd.trim ();

Dies sieht harmlos aus, da der Entwickler annehmen könnte, dass die getProperty () Methode gibt immer etwas zurück. In der Tat, wenn die Eigenschaft "Cmd" existiert nicht, wird ein NULL zurückgegeben, was bei Verwendung eine NULL-Dereferenzierungsausnahme verursacht. Obwohl dies gutartig klingt, kann es dazu führen katastrophale Folgen.

In seltenen Fällen ist das Schreiben oder Lesen von Speicher möglich, wenn NULL der 0x0-Speicheradresse entspricht und privilegierter Code darauf zugreifen kann, was zur Codeausführung führen kann.

Milderungen

Es gibt verschiedene Abhilfemaßnahmen, die Entwickler implementieren sollten. In erster Linie müssen Entwickler sicherstellen, dass Zeiger für Sprachen wie C und C ++ gültig sind, und zwar mit verifizierter Logik und gründlicher Überprüfung.

Für alle Sprachen ist es unbedingt erforderlich, dass Code oder Bibliotheken, die den Speicher manipulieren, Eingabeparameter validieren, um einen Zugriff außerhalb der Grenzen zu verhindern. Im Folgenden finden Sie einige verfügbare Optionen zur Schadensbegrenzung. Entwickler sollten sich jedoch nicht darauf verlassen, dass sie schlechte Programmierpraktiken ausgleichen.

Wahl der Programmiersprache

Einige Sprachen bieten integrierten Schutz vor Überläufen wie Ada und C #.

Verwendung sicherer Bibliotheken

Die Verwendung von Bibliotheken wie der Safe C-Zeichenfolgenbibliothek, die integrierte Überprüfungen zur Vermeidung von Speicherfehlern bieten, ist verfügbar. Es sind jedoch nicht alle Pufferüberläufe das Ergebnis einer Zeichenfolgenmanipulation. Ansonsten sollten Programmierer immer auf Funktionen zurückgreifen, die die Länge von Puffern als Argumente verwenden, z. strncpy () gegen strcpy ().

Kompilierung und Laufzeithärtung

Dieser Ansatz verwendet Kompilierungsoptionen, die der Anwendung Code hinzufügen, um die Verwendung von Zeigern zu überwachen. Dieser hinzugefügte Code kann verhindern, dass zur Laufzeit Überlauffehler auftreten.

Härten der Ausführungsumgebung

Betriebssysteme verfügen über Optionen, um die Ausführung von Code in Datenbereichen einer Anwendung zu verhindern, z. B. einen Stapelüberlauf mit Code-Injection. Es gibt auch Optionen zum zufälligen Anordnen der Speicherzuordnung, um zu verhindern, dass Hacker vorhersagen, wo sich ausnutzbarer Code befinden könnte.

Trotz dieser Abschwächungen gibt es keinen Ersatz für geeignete Codierungspraktiken, um Pufferüberläufe überhaupt zu vermeiden. Daher sind Erkennung und Vorbeugung von entscheidender Bedeutung, um das Risiko dieser Softwareschwächen zu verringern.

Verschieben Sie die Erkennung und Beseitigung von Pufferüberläufen

Die Übernahme eines DevSecOps-Ansatzes für die Softwareentwicklung bedeutet die Integration von Sicherheit in alle Aspekte der DevOps-Pipeline. Genauso wie Qualitätsprozesse wie Codeanalyse und Unit-Tests werden so früh wie möglich vorangetrieben bei SDLC gilt dasselbe für die Sicherheit.

Pufferüberläufe und andere Speicherverwaltungsfehler könnten der Vergangenheit angehören, wenn die Entwicklungsteams einen solchen Ansatz allgemeiner anwenden würden. Wie Untersuchungen von Google und Microsoft zeigen, machen diese Fehler immer noch 70% ihrer Sicherheitslücken aus. Lassen Sie uns unabhängig davon einen Ansatz skizzieren, der sie so früh wie möglich verhindert.

Das Auffinden und Beheben von Speicherverwaltungsfehlern zahlt sich im Vergleich zum Patchen einer freigegebenen Anwendung aus. Der unten beschriebene Ansatz zum Erkennen und Verhindern basiert auf der Verlagerung der Minderung von Pufferüberläufen nach links in die frühesten Entwicklungsstadien. Und dies durch Erkennung durch statische Code-Analyse zu verstärken.

Entdeckung

Das Erkennen von Speicherverwaltungsfehlern beruht auf einer statischen Analyse, um diese Arten von Schwachstellen im Quellcode zu finden. Die Erkennung erfolgt auf dem Desktop des Entwicklers und im Build-System. Es kann vorhandenen Code, Legacy-Code und Code von Drittanbietern enthalten.

Durch die kontinuierliche Erkennung von Sicherheitsproblemen wird sichergestellt, dass alle Probleme gefunden werden, die:

  • Entwickler in der IDE verpasst.
  • Bestehen Sie in Code, der älter ist als Ihr neuer Ansatz zum Erkennen und Verhindern.

Der empfohlene Ansatz ist ein Trust-But-Verify-Modell. Die Sicherheitsanalyse wird auf IDE-Ebene durchgeführt, wobei Entwickler anhand der erhaltenen Berichte Entscheidungen in Echtzeit treffen. Überprüfen Sie als Nächstes auf Build-Ebene. Im Idealfall besteht das Ziel auf Build-Ebene nicht darin, Schwachstellen zu finden. Es soll überprüft werden, ob das System sauber ist.

Parasoft C / C ++ test Dazu gehören statische Analyseprüfer für diese Arten von Speicherverwaltungsfehlern, einschließlich Pufferüberläufen. Betrachten Sie das folgende Beispiel aus dem C / C ++ - Test.

Parasoft C / C ++ - Test Beispiel: Erkennung von Speicherzugriff außerhalb der Grenzen

Parasoft C/C++-Testbeispiel, das die Erkennung eines außerhalb der Grenzen liegenden Speicherzugriffs zeigt.

Vergrößern Sie die Details, die Funktion printMessage () Fehler erkennt den Fehler:

Parasoft-C / C ++ - Test Beispiel für einen Fehler

Der Parasoft C / C ++ - Test bietet auch Ablaufverfolgungsinformationen darüber, wie das Tool zu dieser Warnung gelangt ist:

Parasoft C / C ++ - Test Beispiel für Trace-Informationen

In der Seitenleiste werden Details zum Beheben dieser Sicherheitsanfälligkeit sowie entsprechende Verweise angezeigt:

ParasoftC / C ++ - Test Beispiel für eine Reparatur

Eine genaue Erkennung sowie unterstützende Informationen und Korrekturempfehlungen sind entscheidend, damit statische Analysen und die frühzeitige Erkennung dieser Sicherheitsanfälligkeiten für Entwickler nützlich und sofort umsetzbar sind.

Verhindern von Pufferüberläufen und anderen Speicherverwaltungsfehlern

Die ideale Zeit und der ideale Ort, um Pufferüberläufe zu verhindern, ist, wenn Entwickler Code in ihre IDE schreiben. Teams, die sichere Codierungsstandards wie SEI CERT C für C und C ++ und OWASP Top 10 für Java und .NET oder CWE Top 25 anwenden, haben Richtlinien, die vor Speicherverwaltungsfehlern warnen.

Beispielsweise enthält CERT C die folgenden Empfehlungen für die Speicherverwaltung:

CERT C REC 08 MEM-Liste (Memory Management)

Diese Empfehlungen umfassen vorbeugende Codierungstechniken, mit denen Speicherverwaltungsfehler in erster Linie vermieden werden. Jede Reihe von Empfehlungen enthält eine Risikobewertung sowie Sanierungskosten, sodass Softwareteams die Richtlinien wie folgt priorisieren können:

CERT C REC 08 MEM-Tabelle (Memory Management)

Eine wichtige Präventionsstrategie besteht darin, einen Kodierungsstandard zu übernehmen, der an Branchenrichtlinien wie SEI CERT angepasst ist, und ihn bei künftigen Kodierungen durchzusetzen. Die Verhinderung dieser Sicherheitsanfälligkeiten durch bessere Codierungspraktiken ist billiger, weniger riskant und hat den höchsten Return on Investment.

Das Ausführen einer statischen Analyse des neu erstellten Codes ist schnell und einfach. Es ist für Teams einfach, sich sowohl in die Desktop-IDE als auch in den CI / CD-Prozess zu integrieren. Um zu verhindern, dass dieser Code jemals in den Build aufgenommen wird, sollten Sie zu diesem Zeitpunkt alle Sicherheitswarnungen und unsicheren Codierungspraktiken untersuchen.

Parasoft C / C ++ - Test Beispiel für Sicherheitswarnungen

Screenshot der Parasoft-Integration in die Eclipse-IDE, mit der Schwachstellen auf dem Desktop des Entwicklers verhindert und erkannt werden.

Ein ebenso wichtiger Teil der Erkennung schlechter Codierungspraktiken ist die Nützlichkeit der Berichte. Es ist wichtig, die Grundursache für Verstöße gegen statische Analysen zu kennen, um diese schnell und effizient zu beheben. Hier finden Sie kommerzielle Tools wie Parasofts C / C ++ - Test, dotTEST und Test scheinen.

Die automatisierten Testtools von Parasoft bieten vollständige Spuren für Warnungen, veranschaulichen diese in der IDE und sammeln kontinuierlich Build- und andere Informationen. Diese gesammelten Daten bieten neben Testergebnissen und Metriken einen umfassenden Überblick über die Einhaltung des Codierungsstandards des Teams sowie über den allgemeinen Qualitäts- und Sicherheitsstatus.

Entwickler können Ergebnisse basierend auf anderen Kontextinformationen wie Metadaten zum Projekt, dem Alter des Codes und dem für den Code verantwortlichen Entwickler oder Team weiter filtern. Tools wie Parasoft mit künstlicher Intelligenz (KI) und maschinellem Lernen (ML) verwenden diese Informationen, um die kritischsten Probleme genauer zu bestimmen.

Die Dashboards und Berichte enthalten die Risikomodelle, die Teil der von OWASP, CERT und CWE bereitgestellten Informationen sind. Auf diese Weise verstehen Entwickler besser, welche Auswirkungen die vom Tool gemeldeten potenziellen Schwachstellen haben und welche dieser Schwachstellen priorisiert werden müssen. Alle auf IDE-Ebene generierten Daten korrelieren mit den oben beschriebenen nachgelagerten Aktivitäten.

Zusammenfassung

Der Puffer läuft über und andere Speicherverwaltungsfehler plagen weiterhin Anwendungen. Sie bleiben eine der Hauptursachen für Sicherheitslücken. Trotz des Wissens darüber, wie es funktioniert und genutzt wird, bleibt es weit verbreitet. Siehe die IoT-Hall of Shame für aktuelle Beispiele.

Wir schlagen einen Präventions- und Erkennungsansatz vor, um aktive Sicherheitstests zu ergänzen, die Pufferüberläufe verhindern, bevor sie so früh wie möglich im SDLC in den Code geschrieben werden. Das Verhindern solcher Speicherverwaltungsfehler in der IDE und das Erkennen in der CI/CD-Pipeline ist der Schlüssel, um sie aus Ihrer Software herauszuleiten.

Intelligente Softwareteams können Speicherverwaltungsfehler minimieren. Sie können mit den richtigen Prozessen, Tools und Automatisierungen in ihren vorhandenen Workflows einen Einfluss auf Qualität und Sicherheit haben.

Sehen Sie, wie Parasoft C/C++test Static Analysis Checkers früher im SDLC Pufferüberläufe erkennen und beseitigen.
Kopfschuss von Arthur Hicken, Parasoft-Evangelist

Von Arthur Hicken

Arthur ist seit über 25 Jahren bei Parasoft im Bereich Software-Sicherheit und Testautomatisierung tätig. Er hilft bei der Erforschung neuer Methoden und Techniken (einschließlich 5 Patente) und hilft Kunden dabei, ihre Software-Praktiken zu verbessern.

Erhalten Sie die neuesten Nachrichten und Ressourcen zum Testen von Software sofort.