Im ersten Teil der Blogserie haben wir bereits erläutert, was die grundsätzliche Zielsetzung von Fuzzing ist, welche Qualitätsunterschiede hinsichtlich der Abdeckung möglich sind und auf welche Einschränkungen und Hürden wir in unseren Assessments oft stoßen. Im zweiten Teil wurde der Fuzzer Boofuzz vorgestellt und die Anforderungen für einfache erschöpfende Tests erklärt. Dieser Artikel widmet sich nun einem Implementierungsansatz, wie Boofuzz zum Fuzzen und automatisierten Detektieren von Schwachstellen von Embedded-Webservern verwendet werden kann.

Da wir in der Regel im Namen von Herstellern Geräte testen, können wir bereits auf Entwicklergeräte zurückgreifen, die Zugriff auf Shell-Ebene zulassen. Normalerweise sind solche Entwicklerschnittstellen durch SSH-Server oder UART ausgeführt. Angreifer müssten für dieses Level an Zugriff bereits Schwachstellen ausnutzen oder auf (im Falle von UART) unsichere oder vergessene Entwicklerschnittstellen zurückgreifen. Um den Zugriff zu vereinheitlichen, bietet es sich an, zunächst einen Telnet-Server auf dem Gerät zu starten, sodass der Fuzzer mit möglichst geringem Overhead und mit möglichst geringem Anpassungsbedarf einen Rückkanal zum Arbeiten hat. Sollte kein Telnet auf dem Gerät verfügbar sein, bietet es sich an, eine statische gelinkte Kopie von Busybox beispielsweise über einen USB-Stick einzubringen. Im Falle der O2 Homebox 6441 wurde der Server wie folgt gestartet:

Dieser Server ist die Grundlage für die weitere Untersuchung und ermöglicht direkten Shell-Zugang mit root-Berechtigung:

Für optimale Ergebnisse muss Boofuzz in der Lage sein, Abstürze zu detektieren und den Webserver neu zu starten. Um das über den Telnetserver zu erreichen, muss zunächst eruiert werden, wie der Webserver konkret auf dem Gerät gestartet wird. Dazu wird zunächst der Dienst erkannt:

Scheinbar ist der Webserver httpd und mehrfach gestartet. Es stellte sich jedoch heraus, dass die zusätzlichen PIDs eher als Threads betrachtet werden können.

Als nächstes muss bestimmt werden, wie genau und unter welchem Pfad der Webserver ausgeführt wird:

Scheinbar wird der Webserver also im Verzeichnis /www, ohne Parameter ausgeführt. Es ist also möglich, den Server über Telnet zu beenden und neu zu starten:

Besucht man die Webseite nun im Browser, stellt sich direkt heraus, dass nicht nur der Startvorgang, sondern auch jede Interaktion protokolliert wird:

Hier abgebildet ist nur der initiale Aufruf von /index.htm vom Gerät mit der IP-Adresse 192.168.1.2.

Da NSIDE bereits ein anfälliger Parameter bekannt ist (siehe blogpost), kann direkt der Absturz des Webservers auf diesem Niveau untersucht werden.

Der Parameter _tn war anfällig für eine Buffer-Overflow-Schwachstelle. Der Webserver stürzt beispielsweise beim Besuch der folgenden URL ab:

http://192.168.1.1/cgi/?_tn=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Auf der Shell können die folgenden Ausgaben des Servers eingesehen werden:

Der Webserver gab in diesem Fall also sowohl den anfälligen Parameter als auch den Indikator SIGSEGV aus, was eine typische Fehlermeldung für eine Memory-Corruption-Schwachstelle wie einen Buffer-Overflow ist.

Im Falle dieses Routers war es sogar möglich, noch detailliertere Ausgaben in Form eines Stack-Traces zu erhalten, da der Absturz auf Kernelebene dokumentiert wurde. Dies war möglich durch den Aufruf dmesg unmittelbar nach dem Absturz. Jedes Aufkommen von hex 41 ist dabei potenziell ein Speicherbereich, der kontrolliert werden kann (41 ist die hexadezimale Repräsentation von A):

Besonders interessant in diesem Fall war die Kontrolle über das Register EPC, was bei der MIPS-Architektur der Instruction-Pointer (x86 Äquivalent EIP) ist und bedeutet, dass der Kontrollfluss bereits übernommen wurde.

Durch das Mitlesen auf der Telnet-Schnittstelle und durch die Ausführung von dmesg nach einem Absturz kann der Zustand des Webservers zum Zeitpunkt des Absturzes protokolliert werden. Indem der Webserver direkt wieder gestartet wird, kann selbiger also recht erschöpfend automatisiert getestet werden.

Um diese Aufgaben zu erfüllen, mussten drei Funktionen im Fuzzing-Script implementiert werden. Die Implementierung der Funktionen und das Zusammensetzen zum fertigen Script wird im letzten Teil dieser Serie beschrieben.