Schlamperei in der Softwareentwicklung

25 gefährliche Programmierfehler

05.09.2011
Von 


Joachim Hackmann ist Principal Consultant bei PAC – a teknowlogy Group company in München. Vorher war er viele Jahre lang als leitender Redakteur und Chefreporter bei der COMPUTERWOCHE tätig.

Riskantes Ressourcen Management

Unzureichende Beschränkung von Code auf seinen Speicherbereich: Ein überlaufender Speicherpuffer ist stets wiederkehrende Erinnerung daran, dass ein überquellender Behälter für Unordnung sorgt. Trotz jahrzehntelanger Erfahrung mit der C-Programmierung wurde das Ärgernis Buffer Overflow noch nicht beseitigt. Ein Grund dafür ist, dass Programmierer den Befehl "strcpy" (Kopieren einer Zeichenkette) falsch verwenden oder die Länge des Inputs nicht kontrollieren. Die Technik der Angreifer und die Schutzmechanismen verbessern sich stetig, so dass die heutigen Varianten des Pufferüberlaufs nicht auf den ersten Blick ersichtlich sind. Möglicherweise wähnen sich Programmierer auch immun gegen Buffer Overflow, weil sie eine höhere Programmiersprache als C verwenden. Doch bevor sie sich in Sicherheit wiegen, sollten sie sich fragen, in welcher Sprache ist der Interpreter geschrieben? Was ist mit den nativen Aufrufen von Code? Was ist mit der Software, die auf der Internet-Infrastruktur läuft?

Natürlich sind Programmiersprachen sinnvoll, die nicht so anfällig gegenüber Buffer Overflow sind. Darüber hinaus helfen Bibliotheken, um die Verwendung riskanter APIs zu vermeiden. Beispiele sind die "Safe C String Library" (SafeStr) von Messier und Viega sowie die "Strsafe.h"-Bibliothek von Microsoft. Sie beinhalten Funktionen zur Manipulation von Strings, die sicherer sind als jene, die C bietet.

Sorglose Zustandsverwaltung: Es gibt viele Möglichkeiten, Statusinformationen der Nutzer ohne den ganzen Datenbank-Overhead zu speichern. Unglücklicherweise reduziert das auch den Aufwand für Angreifer, die Daten zu manipulieren. Beispielsweise können die Informationen bequem in Konfigurationsdateien, Profilen, Cookies, versteckten Formularfeldern, Umgebungsvariablen und andern Orten ablegt werden. Doch all diese Speicherplätze lassen sich manipulieren. In zustandslosen Protokollen wie http müssen einige Statusdaten von Anwendern bei jeder Anfrage festgehalten werden, so dass sie zwangsläufig Angriffen ausgesetzt sind. Das ist fatal für sicherheitsrelevante Transaktionen.

Müssen Statusdaten auf Client-Seite gespeichert werden, sollten sie zumindest verschlüsselt werden. Sinnvoll kann zudem die Verwendung von MAC-Algorithmen (Message Authentication Code) wie etwa Hash Message Authentication Code (HMAC).

Externe Zugriffsmöglichkeiten auf Dateinamen und Pfade: Der Datenaustausch erfolgt oft über Dateien. Wenn Dateinamen auf Basis von Benutzereingaben generiert werden, dann könnte der resultierende Pfad außerhalb des vorgesehenen Verzeichnisses liegen. Dies passiert etwa dann, wenn ein Angreifer durch vorangestelltes ".." durch das Dateisystem navigiert. Damit können Programme dazu gebracht werden, Dateien zu lesen und zu verändern. Besonders gefährlich wird es, wenn Programme Dateinamen als Eingabe akzeptieren und dabei mit hohen Nutzerprivilegien laufen.

Ist die Auswahl der Dateinamen begrenzt oder sind verwendbare Dateinamen bekannt, lassen sich feste Eingabewerte mit aktuellen Dateinamen mappen.

Ungeprüft Suchpfade: Suchpfade teilen Anwendungen mit, wo sie kritische Ressourcen wie Code-Libraries oder Konfigurations-Dateien finden. Diese Angaben werden als Umgebungsvariablen gespeichert. Kann ein Angreifer den Suchpfad manipulieren, dann bewirkt er, dass Programme etwa auf falsche Bibliotheken zugreifen.

Um solche Angriffe zu vermeiden, empfiehlt sich eine harte Codierung der Suchpfade.

Einschleusen von Programm-Code (Code Injection): Entwickler finden es oft cool, Teile des Codes dynamisch während der Laufzeit zu erzeugen. Nicht nur für die Programmierer ist dieses Vorgehen attraktiv, auch Angreifer finden daran ihre Freude. Daraus entstehen ernsthafte Schwachstellen, wenn externe Quellen beziehungsweise nicht autorisierte Nutzer eigenen Code einflechten können.

Stellt sich der dynamische Code als Sicherheitsrisiko dar, sollte er entsprechend überarbeitet werden (refactoring). Alternativ kann der Code innerhalb einer Sandbox-Umgebung betrieben werden. Sie schafft Barrieren zwischen Prozess und Betriebssystem.

Code-Download ohne Integritäts-Prüfung: Wer vorgefertigte Programmteile von einer unbekannten Quelle verwendet, handelt fahrlässig. Doch auch verlässliche Sites sind angreifbar, denn Angreifer kennen Tricks, den Programm-Code zu verändern, bevor er auf dem Rechner des Programmierers landet. Seiten können gehackt werden oder mittels DNS-Spoofing umgeleitet werden.

Ein Integritätsscheck kann zumindest den korrekten Transfer vom Host-Server sicherstellen. Das schützt allerdings nicht von anderen Gefahren wie DNS-Spoofing und fehlerhaftem Code auf dem Host.

Unvollständige/lückenhafte Freigabe von Ressourcen: Wenn ein Programm bestimmte Ressourcen nicht mehr benötigt, insbesondere wenn es beendet wird, sollte es diese freigeben beziehungsweise beseitigen. Dazu zählen Speicher, Dateien, Datenstrukturen, Cookies, Sessions und Kommunikationskanäle (Pipes) oder Temporärdateien. Angreifer können sonst diese digitalen Rückstände durchkämmen und dabei an sensible Daten gelangen. Die Wiederverwendung dieser Ressourcen eröffnet ihnen zudem Möglichkeiten zu ihrem Missbrauch.

Falsche Initialisierung: Erhalten Daten und Variablen keine ordentliche Anfangswerte, könnte ein Angreifer diese setzen. Außerdem besteht die Möglichkeit, dass Unbefugte dann Variablenwerte aus einer vorhergehenden Session auslesen. Insbesondere fehlerhaft initialisierte Variablen, die in sicherheitsrelevanten Abläufen verwendet werden, etwa bei der Authentifizierung, könnten dazu verwendet werden, Security-Mechanismen zu umgehen.

Einige Programmiersprachen verlangen es explizit, alle Variablen zu initialisieren, oder tun dies automatisch.

Falsche Berechnungen: Computer können Berechnungen erstellen, die unter mathematischen Gesichtspunkten falsch sind. Wenn etwa zwei große Zahlen miteinander multipliziert werden, kann das Resultat kleiner als beide Zahlen sein, weil damit der Maximalwert des gewählten Datentyps überschritten wird (zum Beispiel Integer overflow). In anderen Fällen werden nicht erlaubte Rechenoperationen angestoßen (Division durch Null). Wenn Kalkulationen ihre Werte über Benutzereingaben erhalten, dann besteht die Gefahr des Missbrauchs. Eine dadurch provozierte Division könnte zum Programmabsturz führen. In kaufmännischen Anwendungen ließen sich möglicherweise negative Preise herbeiführen.

Neben der Eingabevalidierung ist empfehlenswert, nur vorzeichenlose Datentypen zu wählen, wenn ihre Werte nie negativ werden können.