In Anbetracht der zunehmenden Gefahr für die Sicherheit der IT-Anwendungssysteme wird von dem Softwaretest erwartet, dass er einen Großteil der potentiellen Sicherheitslücken aufdeckt. Aber dynamisches Testen ist nicht die einzige Möglichkeit, Sicherheitsmängel im System zu entdecken. So kann die statische Analyse des Codes eine preisgünstige Methode darstellen, potentielle Sicherheitsgefahren aufzudecken. Dadurch können zwar nicht alle, aber ein Teil der potentiellen Sicherheitsmängel erkannt werden. (Hier verwendete Beispiele beziehen sich auf Java)
Zweck der statischen Analyse
Statische Analyse ist einer von vielen Testansätzen, um Mängel in einem Softwaresystem zu finden. Sie ist eine Technik für die Untersuchung der Anforderungstexte, Entwurfsmodelle und Code-Komponente die zu einem Softwareprodukt gehören, ohne das Produkt echt auszuführen. Design-Reviews und Code-Inspektionen basieren auf dem gleichen Prinzip, nur werden sie von Menschen durchgeführt. Eine statische Analyse wird von einem Automaten ausgeführt, der auf verschiedene Regeln prüft. Das Ziel ist Mängel in der Konstruktion der Software vor dem dynamischen Test aufzudecken.
Potentielle Sicherheitslücken im Code
Es stehen mittlerweile viele Werkzeuge für die statische Analyse von Source-Code zur Verfügung. Bei der Auswahl eines geeigneten Werkzeugs stellt sich die Frage nach den Mängelarten, die von dem Werkzeug berichtet werden. Eine weitere Frage ist die nach der Häufigkeit der Falschmeldungen, sogenannte “false positives”. Das sind Meldungen über Mängel, die in Wahrheit keine echten Mängel sind. Wenn Tools viele solcher Meldungen bringen, lenkt das nur von den echten Mängeln ab. Was die echten Mängel betrifft, ist es wichtig, ein Klassifikationsschema zu haben. Aus der Sicht der Codesicherheit gibt es fünf Hauptklassen von Mängeln: Fehlender Code, Unerlaubter Code, Manipulierbarer Code, Fehleranfälliger Code, Nicht-konformer Code.
Fehlender Code
Ein Beispiel von fehlendem Code ist die fehlende Kontrolle der Eingangsparameter von public Methoden. Als erstes wird geprüft ob die Eingangsdaten den richten Typ haben und ob sie im gültigen Wertebereich liegen. Wenn nicht, soll eine Ausnahmebedingung ausgelöst werden.
Das Gleiche gilt für Return-Ergebnisse. Werte die von aufgerufenen Methoden in entfernten Klassen zurückgegeben werden, sollten vor ihrer Verwendung kontrolliert werden.
Eine weiteres Beispiel für fehlenden Code ist das Fehlen der “final” Klausel in einer Klassendefinition. Die Tatsache dass die folgende Klasse nicht mit “final” deklariert ist, erlaubt es einem Eindringling, den Code zu kopieren und zu manipulieren. Um dies zu verhindern, sollten alle untergeordneten Klassen mit “final” deklariert sein.
Mindestens ein Drittel des Codes sollte aus sicherheitsprüfenden Anweisungen bestehen. Ist dies nicht der Fall, ist das ein Zeichen dafür, dass die Ausführung des Codes nicht abgesichert ist.
Unerlaubter Code
Ein Beispiel für unerlaubten Code sind eingebaute Konstanten. Solche Werte im Code zu haben ist nicht wartungsfreundlich und gefährlich. Hacker die Zugriff zum Byte-Code gewinnen, können diese Werte verändern. Deshalb empfiehlt es sich, alle Konstanten in externe Ressourcen auszulagern. Dies gilt besonders für Texte.
Weitere Beispiele für Problemstellen sind Casts und Verschachtelungen.
Manipulierbarer Code
Ein Beispiel für manipulierbarem Code sind eingebaute SQL Anweisungen. Sie sind für SQL Injection Angriffe anfällig, womit Externe den Datenbankinhalt korrumpieren können. SQL Anweisungen haben im Anwendungscode somit nichts zu suchen. Wenn sie überhaupt verwendet werden, dann nur in einer geschützten Zugriffsschicht.
Weitere Beispiele für manipulierbaren Code finden sich in den Bereichen des Cloning und der Deserialisation.
Fehleranfälliger Code
Fehleranfälliger Code kann unter bestimmten Umständen leicht zu einem Fehler führen. Typisch für diese Art Mangel ist der Vergleich eines Objektes mit einem String oder eine Rekursion ohne Endbedingung. Was die Sicherheit anbetrifft, liegt eine potentielle Sicherheitslücke vor, wenn die Anzahl der Parameter beim Funktionsaufruf geringer ist als die Anzahl Parameter in der aufgerufenen Methode. In dem Fall hat der Eindringling die Möglichkeit, eigene Daten an die Parameterliste dranzuhängen und auf diese Weise Viren in den Code zu streuen.
Nicht-konformer Code
Nicht-konformer Code beinhaltet Abweichungen von einer vorgegebenen Norm. Dies ist zwar keine direkte Bedrohung für die Sicherheit, aber in manchen Fällen könnte es eine werden. Nehmen wir das Beispiel wenn bestimmte Standard-Services zu vermeiden sind, weil sie eventuell korrumpiert sind. Wer sie trotzdem in seinem Code anwendet, geht die Gefahr ein, dass er infizierte Objekte übernimmt.
Fallstudie zur Aufdeckung von Codemängeln
Eine Studie wurde an der North Carolina State University mit drei Millionen C++ Codezeilen von der Nortel Networks Corporation durchgeführt um zu zeigen, wie effektiv automatisierte statische Analyse sein kann. Mit dem Werkzeug “Flexlint” hat das Experiment mehr als 136.000 Mängel, bzw. einen Mangel pro 22 Codezeilen aufgedeckt und dass, obwohl der Code schon über drei Jahre im Betrieb war. Die Anzahl der Mängel aus der statischen Analyse korrelierte mit einer Genauigkeit von 83% mit der Anzahl der Fehlermeldungen aus dem PreRelease-Test. Dies spricht dafür, dass die Fehler genauso gut durch die statische Analyse hätten aufgedeckt werden können. Im Gegensatz zum dynamischen Test, der 12 Personenmonate kostete, hätte die statische Analyse nur 2 Personenmonate gekostet.
Erkennung von Sicherheitsmängeln in Java Code
Wie die Studie zeigt, kann die Mehrzahl der Mängel bereits im Code erkannt werden noch ehe getestet wird. Dies gilt ebenso für Sicherheitsmängel, nicht aber für andere nicht-funktionale Mängel wie z.B. Zeitverbrauch, Datenvolumen, Durchlaufkapazität. Werkzeuge, die bei der Prüfung unterstützen, können ohne großen Aufwand in den Entwicklungsprozess integriert werden (z.B. Continuous Integration). Im Zeitalter der agilen Entwicklung und der fortwährenden Auslieferung der Software müssen Mängel schnell und ohne großen Aufwand aufgedeckt und beseitigt werden. Dazu ist die statische Analyse des Codes das beste Mittel.
Folgende Liste soll beispielhaft zeigen, welche Regeln bei solchen Werkzeugen implementiert sind. Sie stammen aus dem Werkzeug JavAudit:
- Vermeide die Allokation nicht-initialisierter Objekte
- Beschränke den Zugang zu Klassen und Methoden
- Finalisiere alle Klassen und Methoden
- Verbiete das Hinzufügen neuer Klassen zu fertigen Paketen
- Verbiete die Verschachtelung von Klassen
- Erlaube keine Signatur – signing – des Codes
- Packe den gesamten Code in einem einzigen Jar
- Definiere alle Klassen als uncloneable
- Definiere alle Klassen als unserializeable
- Definiere alle Klassen als undeserializeable
- Verbiete eingebettete SQL Anweisungen
- Verbiete das Casting von Datentypen
- Prüfe alle Eingangsargumente
- Prüfe alle Return-Werte
- Stelle alle IO-Operationen in try..catch Klammern
- Stelle alle Aufrufe entfernter Methoden in try…catch Klammern
- Vergleiche den Typ und die Anzahl Parameter in jeder Schnittstelle
- Vermeide die Nutzung hard-codierter Datenwerte
Schlußfolgerung
Erfahrungen mit der statischen Code-Analyse zeigen, dass viele Sicherheitsmängel in einem IT System schon im Code sichtbar sind. Die statische Analyse ist ein preiswerter, schneller und zuverlässiger Weg, diese Sicherheitsmängel aufzudecken. Entwickler brauchen lediglich die richtigen Werkzeuge und den festen Willen, sauberen Code zu produzieren.
Häufig gestellte Fragen
Kann statische Codeanalyse den dynamischen Test bei Sicherheitsprüfungen ersetzen?
Nein. Die statische Analyse deckt nicht alle, aber einen Teil der potentiellen Sicherheitsmängel auf, und zwar bevor überhaupt getestet wird. Für andere nicht-funktionale Eigenschaften taugt sie nicht: Zeitverbrauch, Datenvolumen und Durchlaufkapazität lassen sich nur dynamisch prüfen. Sie ist also eine preisgünstige Ergänzung, die dem dynamischen Test vorausgeht, kein Ersatz.
Worauf kommt es bei der Auswahl eines Werkzeugs für die statische Codeanalyse an?
Zwei Fragen entscheiden: Welche Mängelarten meldet das Werkzeug, und wie häufig sind Falschmeldungen? Sogenannte false positives sind Meldungen über Mängel, die in Wahrheit keine sind. Bringt ein Werkzeug viele davon, lenkt es von den echten Mängeln ab. 2015 stand bereits eine große Auswahl an Werkzeugen zur Verfügung, was den Vergleich anhand dieser beiden Kriterien nötig machte.
Welche Arten von sicherheitsrelevanten Codemängeln lassen sich unterscheiden?
Aus Sicht der Codesicherheit gibt es fünf Hauptklassen: fehlender Code, unerlaubter Code, manipulierbarer Code, fehleranfälliger Code und nicht-konformer Code. Ein solches Klassifikationsschema hilft, die Meldungen eines Analysewerkzeugs zu ordnen. Nicht-konformer Code, also die Abweichung von einer vorgegebenen Norm, ist dabei keine direkte Bedrohung, kann aber zu einer werden.
Wie viel Prüflogik sollte sauberer Anwendungscode enthalten?
Mindestens ein Drittel des Codes sollte aus sicherheitsprüfenden Anweisungen bestehen. Fällt der Anteil deutlich darunter, ist das ein Zeichen dafür, dass die Ausführung nicht abgesichert ist. Dazu gehören die Kontrolle der Eingangsparameter öffentlicher Methoden auf Typ und gültigen Wertebereich samt Auslösen einer Ausnahmebedingung sowie die Prüfung von Rückgabewerten aus entfernten Klassen vor ihrer Verwendung.
Warum sind fest eingebaute Konstanten im Quellcode ein Sicherheitsproblem?
Hardcodierte Werte sind nicht wartungsfreundlich und angreifbar: Wer Zugriff auf den Byte-Code gewinnt, kann sie verändern. Deshalb gehören alle Konstanten in externe Ressourcen ausgelagert, besonders Texte. Der Artikel zählt eingebaute Konstanten zur Klasse des unerlaubten Codes, zusammen mit Problemstellen bei Casts und Verschachtelungen.
Warum gelten SQL-Anweisungen im Anwendungscode als riskant?
Eingebettete SQL-Anweisungen sind anfällig für SQL-Injection-Angriffe, mit denen Externe den Datenbankinhalt korrumpieren können. Im Anwendungscode haben sie deshalb nichts zu suchen. Werden sie überhaupt verwendet, dann nur innerhalb einer geschützten Zugriffsschicht. Zur selben Klasse des manipulierbaren Codes zählen auch Schwachstellen beim Cloning und bei der Deserialisation.
Wie kann eine falsche Parameteranzahl beim Methodenaufruf zur Sicherheitslücke werden?
Ist die Anzahl der Parameter beim Funktionsaufruf geringer als die Anzahl der Parameter in der aufgerufenen Methode, kann ein Eindringling eigene Daten an die Parameterliste anhängen und auf diesem Weg Viren in den Code streuen. Solche Konstruktionen gehören zum fehleranfälligen Code, ebenso wie der Vergleich eines Objektes mit einem String oder eine Rekursion ohne Endbedingung.
Lohnt sich statische Analyse noch bei Code, der seit Jahren produktiv läuft?
Ja. In einer Studie der North Carolina State University wurden drei Millionen C++-Codezeilen der Nortel Networks Corporation mit dem Werkzeug Flexlint untersucht. Sie deckte über 136.000 Mängel auf, also einen pro 22 Codezeilen, obwohl der Code bereits mehr als drei Jahre im Betrieb war. Die Mängelzahl korrelierte zu 83 Prozent mit den Fehlermeldungen aus dem PreRelease-Test, kostete aber 2 statt 12 Personenmonate.


