Input-Validierung bei der Entwicklung Tainted Data identifizieren
Angriffe auf Websites, Anwendungen oder sogar auf moderne Autos erfolgen meist über das Einschleusen manipulierter Daten. Für sichere Software ist es also wichtig, alle potenziellen Input-Quellen zu kennen und entsprechend ihrer Vertrauenswürdigkeit zu behandeln. Bei der Kontrolle dieser „Tainted Data“ helfen Tools zur statischen Code-Analyse.
Anbieter zum Thema

Angreifer versuchen meist, Schadcode mithilfe manipulierter Daten in das System einzuschleusen, um so die Kontrolle über ein Programm zu erlangen. In der Regel muss ein Angreifer dazu Eingabewerte des angegriffenen Programms, die zum Beispiel von Sensoren, anderen Anwendungen oder auch von Benutzern kommen, für seine Zwecke manipulieren.
Um Software sicher zu machen, ist also eine möglichst geringe Fehlerquote notwendig, um die Angriffsfläche klein zu halten. Allerdings bleiben selbst bei der so genannten Reinraum-Entwicklung noch immer 0,1 Fehler pro 1000 Code-Zeilen übrig, üblich sind heute jedoch bei vollständig getesteten, proprietären Programmen ein Fehler auf 1000 Zeilen.
Bei Open Source liegt die Fehlerquote bei 0,68 pro 1000 Zeilen. Und das hat weitreichende Folgen. So wächst der Linux-Kernel zum Beispiel mit jeder Version um durchschnittlich 290.000 Zeilen. Und damit statistisch um über 190 neue Fehler.
Aus Fehlern werden Angriffsflächen
Häufig ergibt sich aus Fehlern Angriffsfläche. Diese wird ausgenutzt, um Schadcode in das System einzuschleusen und so die Kontrolle zu erlangen. Buffer Overflows sind die wohl bekanntesten Vertreter dabei. Aber auch Web-Entwickler können ein Lied davon singen, welche Auswirkungen SQL-Injections haben können.
Neben der Fehlervermeidung ist also der Umgang mit Daten aus unklaren Quellen extrem wichtig, um sicheren Code zu erhalten. Dabei sollte die Regel gelten: Daten, die nicht verifiziert und vertrauenswürdig sind, gelten als „tainted“, also schmutzig. Erst wenn diese Daten validiert wurden, können sie als sauber und zuverlässig gelten.
Welche Quellen potenziell risikoreich sind, hängt von der Nutzung eines Systems ab. Grundsätzlich sind Nutzereingaben als möglicherweise gefährlich anzusehen, ebenso von extern übertragene Daten. Auch der Output von Sensoren oder anderen Systemen, die mit dem Zielsystem kommunizieren, sollte zunächst als „tainted“ betrachtet werden.
Ein einfaches Beispiel für einen Fehler, der es einem Angreifer ermöglicht, mit Tainted Data zu attackieren, ist diese kleine Prozedur:
void config (void) {
char buf[100];
int count;
…
strcpy(buf, getenv(“CONFIG“);
…
}
Der Input aus einer hier undefinierten Quelle kommt durch einen Call der Funktion getenv, die den Inhalt der Umgebungsvariablen CONFIG ausliest. Der Programmierer geht davon aus, dass der Inhalt der Umgebungsvariablen in den Puffer passt. Allerdings wird die Größe der Variablen nicht überprüft. Ist sie länger als 100 Zeichen, kommt es zum Buffer Overflow.
Ein Angreifer, der CONFIG manipulieren kann, kann so auch das fragliche Programm angreifen, ohne darauf direkt Zugriff zu haben: Da „buf“ eine automatische Variable ist, die gleich am Anfang des Stacks abgelegt wird, wird jedes Zeichen, das über die erlaubten 100 hinausgeht, außerhalb der Grenzen des Puffers des Programms geschrieben.
Dabei könnte die Variable count überschrieben werden – je nachdem, wie der Compiler den verfügbaren Platz im Stack zuweist. Geschieht dieses, kontrolliert der Angreifer den Wert dieser Variablen.
Systemübernahme durch Tainted Data
Das alleine wäre schon schlimm genug. Allerdings beinhaltet der Stack auch die Adresse, an die das Programm nach der Ausführung dieser einen Prozedur springen wird. Um darüber eine Attacke zu starten, kann der Angreifer den Wert der Variablen auf einen speziell dafür entworfenen String setzen, der eine Return-Adresse nach den Wünschen des Hackers beinhaltet.
Ist die Funktion dann durchlaufen, wird die manipulierte Adresse an den Caller der Funktion zurückgeliefert. Der Angreifer kann so eingeschleusten Schadcode zur Ausführung bringen, Systeme kontrollieren und sich von einem System über die vorhandenen Schnittstellen zum nächsten weiterarbeiten. Innerhalb einer geschützten Umgebung, in der der Hacker nicht die Umgebungsvariablen kontrollieren kann, würde der Angriffsvektor wohl nicht funktionieren.
Trotzdem stellt der Code eine Gefahr dar. Kommt der Input aus einer anderen Umgebung, etwa über das Dateisystem oder eine Netzwerkschnittstelle, könnte er dennoch manipuliert sein. Zudem könnte der Code zum Beispiel in einem anderen Programm wiederverwertet werden, das nicht in einer geschützten Umgebung abläuft.
Datenströme automatisch prüfen
Es gilt also, Tainted Data möglichst abzufangen, bevor ein Programm ausgeführt wird. Am effizientesten geschieht das frühzeitig innerhalb des Software Development Lifecycle (SDLC) mittels geeigneter automatisierter Analyseverfahren. Taint-Analysen zählen zu den statischen Analysen und sind Bestandteil von Tools wie CodeSonar von GrammaTech.
Im Gegensatz zum Testing wird bei der statischen Analyse ein Programm nicht ausgeführt. Das Tool zur statischen Analyse erstellt aus dem Code ein Modell, das systematisch durchlaufen und mittels Checkern auf definierte Probleme und Fehler hin überprüft wird. Dabei werden auch die Daten- und Steuerungsflüsse innerhalb des Programms berücksichtigt – unabhängig davon, ob diese auch in einem konkreten Einsatzszenario durchlaufen würden.
Wird eine Anomalie erkannt, erfolgt eine Warnung sowie die Identifikation des Codepfades, der zu dem Ereignis führt. Die Verfolgung von Datenströmen durch den Code ist dabei eine Herausforderung. Es müssen zahlreiche Aspekte betrachtet werden, etwa die Übergabe der Werte zwischen verschiedenen Variablen. Dieses muss zudem über Prozedurgrenzen hinweg und über verschlungene Umwege möglich sein.
Strings in C beispielsweise werden üblicherweise durch Pointer verwaltet. Die Analyse muss also die Inhalte der Strings überwachen und die Werte aller Pointer, die auf diese Strings verweisen. Und selbstverständlich kann es Pointer geben, die auf einen Pointer verweisen, die auf einen Pointer verweisen und so fort. Diese möglichen Rekursionen müssen ebenfalls berücksichtigt werden.
Sichere Software ist bessere Software
Um Daten und Datenquellen zu validieren, kommen prinzipiell zwei Aspekte zum Einsatz: Zum einen muss geprüft werden, wie die Daten innerhalb des Programms fließen und wie sie vor allem die sensiblen Codeteile erreichen. Zudem ist zu ermitteln, ob die Daten innerhalb eines zu erwartenden Bereichs liegen. Manuell wird das ab einer gewissen Komplexität auch den erfahrensten Entwicklern kaum mehr gelingen.
Nicht zuletzt deswegen empfehlen Standards wie MISRA-C den Einsatz von statischer Analyse innerhalb des SDLC. Tools wie CodeSonar von GrammaTech sind in der Lage, Programmierfehler und potenziell gefährliche Datenströme automatisch zu erkennen – sowohl im Quellcode als auch im Binärcode. Damit lässt sich die statische Analyse frühzeitig im SDLC als fortlaufender Prozess verankern. Gerade bei Entwicklung kritischer Software und Devices ist diese Sicherheitsschicht unverzichtbar, um zuverlässige Produkte zu schaffen.
* Mark Hermeling ist Senior Director Product Marketing bei GrammaTech, Inc.
(ID:45048393)