Secure Coding ist nicht mehr neu in der Softwareentwicklung. Es ist auch längst etabliert, man findet reichhaltige Informationen dazu, es gibt Kurse, Tutorials, und alles ist bestens dokumentiert. Alle Entwicklerinnen und Entwickler sollten es daher schon längst einsetzen. Leider beginnt die Entwicklung sicherer Software nicht mit einer einfachen To-Do Liste und kann nicht ohne die notwendige Unterstützung auf allen Ebenen moderner Anwendungen bewältigt werden, die aus einer Vielzahl von Komponenten und Wechselwirkungen bestehen. Warum das so ist und wie man beginnen kann, wollen wir hier kurz skizzieren.
Code ist auf sich alleine gestellt
Wenn Code einmal die Entwicklungs- und Testumgebung verlassen hat, dann muss diese zur Laufzeit alleine und ohne Aufsicht funktionieren. Computersysteme sind ja genau für automatisierte Prozesse, die größtenteils unbeaufsichtigt durchgeführt werden. Nur in Fehler- oder Ausnahmefällen erzeugt Software Meldungen bzw. Ergebnisse, die dann von einem Menschen bearbeitet werden müssen. Man muß also bei der Entwicklung möglichst gut zu Zukunft voraussehen und Situationen vorbeugen, die möglicherweise auftreten können. Genau da beginnt der Bereich, in dem potentielle Sicherheitslücken ein gutes Versteck finden.
Um unbekannte Bedrohungen zu illustrieren, wird gerne das Internet zitiert. Tatsächlich kann man jedwede Produktionsumgebung zu diesem Zweck verwenden, denn selbst im aufgeräumtesten Netzwerk findet man ausreichend komplexe Protokolle, Daten und Situationen, die nicht geplant waren. Software darf in keiner Umgebung versagen. Der Programmablauf muss zu jeder Zeit einen konsistenten Zustand vorfinden. Eine Testumgebung muss diesem Umstand Rechnung tragen. Dabei darf man nicht vergessen, dass Software mitunter Jahre oder Jahrzehnte im Einsatz ist. Bei diesen Zeitspannen kann man nicht alles vorhersehen. Bestes Beispiel sind Standards und Spezifikationen. Unicode ist ein Industriestandard für konsistentes Kodieren, Repräsentieren und Bearbeiten von Text der meisten weltweit eingesetzten Schriften. Der aktuelle Standard 11.0 von 2018 besteht aus 137.374 Zeichen. Im Vergleich zur Version 10.0 aus dem Jahr 2017 sind 684 neue Zeichen hinzugekommen. Wie viele werden es im nächsten Standards sein, welche Bedeutung werden sie haben, und welche Codes werden ihnen zugeordnet? Darf meine Applikation dennoch von sich behaupten Unicode-Unterstützung zu beherrschen? Man kann sich also getrost in aktueller Software Gedanken machen, was genau mit unbekannten Kodierungen zu tun ist, weil dieses Problem bestehen bleiben wird. Fälle wie diese lassen sich mit beliebigen Datenformaten und Übertragungsprotokollen wiederholen.
Konsistente Zustände
Eine Applikation soll stetig und ständig konsistent sein. Diese theoretische Anforderung ist ein guter Start in die Welt des Secure Coding und Secure Design. Code besteht aus Komponenten und läuft in der Regel mit Hilfe eines Betriebssystems. Das bedeutet, dass dauernd Funktionen aufgerufen werden, die Ressourcen bearbeiten oder Aufgaben verteilen. Sofern alle Operationen fehlerlos durchgeführt werden können, gibt es bei den Fehlerabfragen nichts zu tun. Nur die eigenen Datenstrukturen des Codes müssen unterhalten werden. Das ist die erste Aufgabe der Entwickler. Auch hier ist kreatives Denken gefordert. Was passiert, wenn das Programm nicht beendet wird? Wir haben in Produktivumgebungen schon Systeme gesehen, die seit 7 Jahren ununterbrochen im Einsatz sind. Speziell bei Telefonanlagen oder Infrastruktur ist dies häufig zu beobachten. Das bedeutet mehr als 220.752.000 Sekunden Ablauf von Instruktionen, Datenzugriffe und ‑verarbeitung. Unit Tests können solche Szenarien nicht abbilden, weil die Zeiten zu lange und dadurch die Möglichkeiten der Code-Pfade zu vielfältig sind.
Fokus auf Ausnahmesituationen
Sind die Daten „normal“, so werden Tests in der Regel funktionieren und Fehler finden. In den Randgebieten, wo Ausnahmesituationen herrschen, wird es für jede Applikation spannend. Der Sinn von Testfällen ist ja das Aufspüren von Software Bugs und das Detektieren, ob behobene Fehler auch solche bleiben. Bei Secure Coding möchte man jedoch den Code mit Situationen fordern, die noch nicht aufgetreten sind. Das lässt sich nur mit automatisierten Tests erreichen, die stetig Eingabedaten verändern und prüfen, ob die Software damit zurechtkommt. Die Methode nennt sich Fuzzing. Sie ist schon sehr alt und stammt aus der Zeit der Lochkarten. Damals hat man defekte und absichtliche abgeklebte bzw. zusätzlich gelochte Lochkarten als Eingabe verwendet. In der Informatik hat dieser Ansatz in den letzten Dekaden eine Renaissance erlebt. Fuzzing zählt heute zum Standardrepertoire der Softwareentwicklung.
Der Einstieg ist denkbar einfach, da man ja ohnehin bei Continuous Integration und Build Tests Automatiken hat. Fuzzing erweitert diesen Prozess um algorithmisch zufällig variierte Eingaben. Ausgangsbasis sind normale Daten, die man als Beispiel verwendet. Man kann den Testkorpus zusätzlich mit Extremen anreichern (besonders große, verschachtelte oder sonstige Daten, die üblicherweise nie/selten auftreten, jedoch an die Grenzen des Datenformats gehen). Der Vorteil ist das Testen ohne Betreuung sowie die Generierung von weiteren Testdaten, falls der Prozess Fehler gefunden hat. Die Integration von Fuzzing ist der leichteste Einstieg in das Thema Secure Coding. Finden Sie die Daten, die Ihre Anwendung bedrohen, bevor es die Angreifer tun.
René Pfeiffer ist freier Mitarbeiter bei SEC4YOU im Bereich Penetration Testing, IT-Sicherheitsberatung und Secure Coding. Regelmäßig beweißt er seine Kompetenz in anspruchsvollen Security Projekten. Für Fragen erreichen Sie René Pfeiffer über unsere Kontaktmöglichkeiten.