Wer kennt das nicht – das Release Datum für die neue Software steht vor der Tür und man muss die gerade erstellte Software „nur“ noch Testen…
Zum Glück wurden alle Testcases schon im Voraus definiert und man kann nach Testdrehbuch alle vorgesehenen Tests abarbeiten und alle automatisierten Tests durchführen. Wenn alle Tests „grün“ sind, kann man sagen, dass die Software getestet wurde und somit bereit ist für die Verteilung.
Was ist aber wenn ein wichtiger Testcase vergessen wurde, und der Kunde meldet, dass die Software ständig abstürzt? Oder noch schlimmer: was ist, wenn der Kunde meldet, dass durch eine Lücke in der neuen Software ein Angreifer in das System vom Kunden eingebrochen ist?
Die Antwort auf diese Fragen ist, dass man mühselig den Fehler beheben und eine neue Version dem Kunden ausliefern muss. Bei sicherheitsrelevanten Fehlern muss dieser Prozess möglichst schnell passieren!
Besser wäre, wenn man schon im Vorhinein solche Bugs beheben könnte, bevor die Software beim Kunden ausgeliefert wird.
Es ist eher unwahrscheinlich, dass man alle Fälle mit entsprechenden Tests abdecken kann. Es gibt aber eine gute Methode, wie man ohne grossen Aufwand Software auf ihre Robustheit und Sicherheit überprüfen kann – das Stichwort heisst Fuzzing.
Fuzzing ist eine bewährte Methode, Software auf ihre Robustheit zu testen sowie um eventuelle Sicherheitslücken aufzuspüren (Siehe auch https://de.wikipedia.org/wiki/Fuzzing).
Viele Hersteller setzen auf diese Methode um Software zu testen, vor allem bei Software, bei der das Schadensausmass sehr hoch wäre, wie zum Beispiel bei Browsern oder Office Anwendungen.
Hier ist ein Beispiel einer kritischen Sicherheitslücke, welche ich mit Hilfe von Fuzzing aufdecken konnte (https://www.mozilla.org/en-US/security/advisories/mfsa2011-29/ ).
In diesem Artikel möchte ich kurz zeigen, wie einfach eine Software mit Hilfe von Fuzzing auf Robustheit und potentielle Sicherheitslücken getestet werden kann.
Die zu testende Applikation
Als Beispiel werde ich einen Klassiker der Sicherheitslücken zeigen, welchen wir mit Hilfe von Fuzzing aufdecken werden.
Unsere zu testende Beispielapplikation in C/C++ ist nicht sonderlich komplex. Sie hat einzig die Aufgabe einen Wert einzulesen und diesen wieder in einer bestimmten Form anzuzeigen. Konkret soll die Applikation einen Namen einlesen und den Benutzer mit „Hallo“ begrüssen. Der Name wird der Applikation als Argument beim Programmstart übergeben.
Hier das Codebeispiel:
#include "stdafx.h" #include <iostream> using namespace std; void printInput(char* input) { char buf[20]; strcpy(buf, input); printf("Hallo %s\n", buf); } int _tmain(int argc, _TCHAR* argv[]) { if (argc > 1) { printInput(argv[1]); } return 0; }
Um dieses Beispiel mit Visual Studio kompilieren zu können, müssen in den Projekteinstellungen folgende Einstellungen zurückgesetzt werden:
Schon hier sollte jeder Entwickler stutzig werden, wenn an dieser Konfiguration etwas verändert werden muss. Aber in diesem Beispiel geht es ja genau darum, wie man es nicht machen sollte.
An dieser Stelle sei erwähnt, dass der Compiler eine Meldung Zeigen würde, dass etwas an diesem Code nicht in Ordnung ist:
Warning 1 warning C4996: ’strcpy›: This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
Anschliessend muss noch „Character Set“ auf „Not Set“ gesetzt werden und unsere Demo Applikation kann kompiliert und verwendet werden.
Als positiv Test geben wir den Namen „noser“ als Argument mit und erhalten den erwarteten Output:
Der Fuzzer
Es gibt viele verschiedene Arten von Fuzzer und die meisten sind leicht über Google auffindbar.
Für das Testen unserer Demo Applikation verwenden wir Radamsa (https://github.com/aoh/radamsa).
Für Windows kann eine experimentelle Version verwendet werden, welche hier gefunden werden kann (https://code.google.com/archive/p/ouspg/downloads)
Die Verwendung von Radamsa ist ziemlich einfach. Ich verwende hier die Konsolen-Applikation für die Generierung der Testfälle.
Hier ein Beispiel wie so ein Testfall aussehen könnte:
echo noser | radamsa-0.4.exe
Als Eingabewert verwende ich das Wort „noser“. Dieser wird über eine Pipe-Anweisung „|“ an Radamsa übergeben. Dort wird das eingegeben Wort zu einer zufälligen Ausgabe umgewandelt/mutiert.
Genau diese zufällige Ausgabe brauchen wir um unsere Demo Applikation zu testen.
Für vier Testfälle musst der Befehl vier Mal manuell in der Konsole eingeben werden. Wenn man viele Tests durchführen will und die Demo Applikation automatisch testen möchte, kommt man mit der manuellen Eingabe nicht weit.
Aus diesem Grund habe ich ein PowerShell-Script erstellt, welches genau das macht was wir brauchen.
Ein Wert wird an Radamsa übergeben, und die Ausgabe von Radamsa wird direkt unserer Demo Applikation als Argument übergeben:
for($x=0; $x -le 10;$x++) { $var= echo noser | .\radamsa-0.4.exe echo $x echo $var .\TestApplicationFuzzing.exe $var }
Der Wert in der For-Schleife kann natürlich noch erhöht werden, wenn man mehr Testfälle ausführen will.
Jetzt kann unsere Demo Applikation mit dem PowerShell Script automatisch auf Robustheit und Sicherheitslücken getestet werden:
Schon bei der Ersten Ausführung von unserem Script stürzt die Demo Applikation ab.
Der Testfall 8 scheint in diesem Fall einen Absturz zu verursachen. (Die anderen Testcases ignorieren wir mal)
Versuchen wir den Absturz manuell zu reproduzieren, um sicher zu sein, dass dieser Testfall tatsächlich zum Absturz geführt hat:
Tatsächlich führ dieser Testcase zu einem Absturz. Anscheinend verträgt unsere Applikation folgende Eingabe nicht:
Oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooser
Hallo oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooooooser
Die Analyse
Der Grund für den Absturz kann einfach mit WinDbg (siehe hier: https://msdn.microsoft.com/en-us/library/windows/hardware/ff551063(v=vs.85).aspx) eruiert werden.
Ein „stack buffer overrun“ ist der Grund für den Absturz.
Solche Fehler sind immer als kritisch zu betrachten und müssen auf jeden Fall behoben werden
(siehe hier https://en.wikipedia.org/wiki/Stack_buffer_overflow und hier https://en.wikipedia.org/wiki/Buffer_overflow)!
Ein Blick in den Code zeigt uns was die Ursache des Problems ist:
Der allozierte Speicher (buf[20]) reicht gerade mal für 20 Zeichen (inkl. String Termination = ‹\0›, siehe hier https://en.wikipedia.org/wiki/C_string_handling )
Wenn als Argument ein String mitgegeben wird, der länger ist als 20 Zeichen, führt dies zu einem Puffer überlauf, weil die Funktion strcpy, welche in diesem Beispiel verwendet wird, keine Längenüberprüfung vom Input macht.
Fazit
Fuzzing ist eine sehr gute Methode, um eine Software auf Fehler und Sicherheitslücken zu überprüfen. Der Einsatz von Fuzzing-Tools ist einfach und schnell in jedem Entwicklungsprozess integriert.
Es gilt aber noch zu erwähnen, dass Fuzzing alleine nicht ausreicht um Fehler in einer Software zu finden und daher ist es wichtig, dass die bisherigen Testmethoden wie zum Beispiel Unit- und Integrationstests weiterhin durchgeführt werden und Fuzzing als eine weitere, unterstützende Testing-Methode verwendet wird.
[…] und AFL Wie aus meinem früheren Artikel ersichtlich ist, bin ich ein grosser Fuzzing Enthusiast und konnte diese Sicherheitslücke von […]