Schlamperei in der Software-Entwicklung

25 gefährliche Programmierfehler

22.09.2011 von Joachim Hackmann
Projekte zur Software-Entwicklung stehen häufig unter Zeitdruck. Zwangsläufig kommt es da zu Fehlern. Wir haben für Sie eine Liste mit den 25 häufigen Bugs zusammengestellt.
Hier finden Sie eine Liste von 25 häufigen Bugs sowie Tipps, sie zu vermeiden.
Foto: fkprojects - Fotolia.com

Unsere Liste wurde von verschiedenen renommierten Organisationen und Unternehmen zusammengestellt. Federführend haben das SANS Institut (SysAdmin, Audit, Network, Security), eine auf IT-Sicherheit spezialisierte Genossenschaft, sowie MITRE mitgewirkt. MITRE betreibt die Web-Site auf der Common Weakness Enummeration, auf der die Liste erschienen ist. Zudem haben Experten etwa Apple, Microsoft, Oracle, EMC, McAfee und Symantec mitgewirkt. Außerdem waren die National Security Agency (NSA), das Computer Emergency Response Team (CERT) und das Open Web Application Security Project (OWASP) beteiligt.

Die Autoren haben Fehler in folgende drei Kategorien sortiert:

Die Fehler, zum Teil durch eine schlampige Programmierung verursacht, können es Angreifern ermöglichen, in IT-Systeme einzudringen, Software unter ihre Kontrolle zu bringen oder Daten zu manipulieren.

Unsichere Interaktion zwischen Komponenten

Unzureichende Eingabevalidierung: Die Möglichkeit, falsche Daten in ein Eingabefeld einzutragen, ist der Killer Nummer eins für fehlerhafte Software, schreibt das SANS-Institut. Gemeint sind fehlende Prüfroutinen, wenn etwa die Software etwa eine Zahlenangabe erwartet, der User aber fälschlicherweise Buchstabenfolgen eingibt. Hier versäumen es Programmierer schon mal, eine entsprechende Vorsorge zu treffen und unzulässige Eingabe abzufangen. Komplexe Applikationen bieten eine Vielzahl von Möglichkeiten zur falschen Dateneingabe. Oft sind die Fehlerquellen schwer zu erkennen.

Das Institut empfiehlt Tools zur Eingabevalidierung wie etwa das Framework Apache Struts oder die Methodensammlung ESAPI des Open Web Application Security Project (OWASP).

Ungenaue Kodierung und Maskierung der Ausgabedaten: Dieser Programmierfehler ist Pedant zur fehlenden Eingabevalidierung. Wenn ein Programm eine Ausgabe erzeugt, die anderen Komponenten als Input dient, dann sollten Anweisungen und Metainformationen von den eigentlichen Daten getrennt werden. Ein weit verbreitetes Vergehen besteht darin, alle diese Informationen in einem Datenstrom einzustellen und nur durch spezifische Trennzeichen voneinander abzugrenzen. Dies kann Angreifern die Möglichkeit eröffnen, die integrierten Anweisungen, etwa SQL-Statements, zu manipulieren.

Das SANS-Institut rät daher Programmierern, explizit Encoding-Zeichen zu setzen, wann immer das Protokoll es erlaubt.

Unsichere SQL-Statements (auch bekannt als SQL-Injection beziehungsweise SQL-Einschleusung): Wenn Angreifer die SQL-Abfragen manipulieren können, fällt es ihnen leicht, sich Zugang zu wesentlichen Informationen zu verschaffen. Werden SQL-Queries für Sicherheitsfunktionen wie Authentifizierung verwendet, können Angreifer diese verändern und sich unerlaubten Zugriff auf dieses System verschaffen. Abhilfe schaffen Datenbank-Frameworks und Shared Procedures. Zudem sollten Entwickler den Einsatz von Persistenzlösungen wie Hibernate in Erwägung ziehen.

Fehlerhafte Web-Seiten-Programmierung (Cross-site Scripting): Das Cross-site scripting (XSS) gehört zu den gängigen, hartnäckigen und gefährlichen Schwachstellen von Web-Anwendungen. Basis dafür ist die Kombination von http, Parametern und Scripts zur Erzeugung dynamischer Web-Seiten. Wurde sie nicht sorgfältig implementiert, können Angreifer beispielsweise Javascript einschleusen, das von Browsern der Web-Seiten-Besucher ausgeführt wird.

Unsicherer Aufruf von externen Programmen: Oft arbeitet eine Applikation als Mittler zwischen Anfragen, die von außen kommen, und den internen Funktionen des Betriebsystems. Dies ist der Fall, wenn Applikationen native Programme des Betriebssystems aufrufen und dabei nicht verhindern, dass unzulässige Eingaben in das Kommando eingeschleust werden. Angreifer könnten damit die Herrschaft über das Betriebssystem übernehmen. Wo möglich sollten daher Bibliotheken anstatt externer Prozesse aufgerufen werden.

Klartext-Übertragung sensibler Daten: Zum Teil versenden Applikationen sensible Daten unverschlüsselt über das Netz. Sniffer-Tools sind weit verbreitet, so dass Interessenten den Datenverkehr lesen können. Die Informationen mit URL-Kodierung oder Base64 zu verschlüsseln bietet keinen Schutz. Sensible Daten sollten immer verschlüsselt werden. Web-Applikationen mit SSL sollten so gestaltet werden, dass die gesamt Transaktion und nicht nur der Anmeldeprozess verschlüsselt wird.

Anfälligkeit für Cross-Site Request Forgery (CSRF): Kaum jemand würde am Flughafen einen Koffer mit unbekanntem Inhalt von einem Fremden mit entgegen nehmen. Genau das macht die Cross-Site Request Forgery, nur das der Angreifer Daten einer Webanwendung manipuliert, indem er den Zugang berechtigter User nutzt. Der Web-Browser des Opfers setzt wie vom Angreifer gewünscht HTTP-Request an die Web-Anwendung ab. Gefährlich wird es dann, wenn der angemeldete Benutzer über Administrator-Rechte verfügt.
Als Gegenmittel gibt es Anti-CSRF-Pakete wie CSRFGuard vom OWASP.

Unbeabsichtigte Wettlaufsituationen (Race Condition): Verkehrsunfälle entstehen, wenn zwei Fahrzeuge zur exakt gleichen Zeit den genau gleichen Fahrbahnabschnitt nutzen wollen. So etwas passiert auch im Softwarebetrieb. Oft kollidieren mehrere Prozesse, wobei der Angreifer die Kontrolle über einen Prozess hat. Die Konkurrenz der Prozess kann zum Denial of Service führen, also zum Totalausfall der System. Die Probleme können sich über das Web auf andere Seiten fortpflanzen.

Einige Programmiersprachen implementieren Synchronisierungsfunktionen. Eine andere Lösung ist es, Spring-Abstraktionen zu nutzen, um die Threads zu sichern.

Schwachstellen in Fehlermeldungen: Allzu viel sagende Fehlermeldungen bieten Angreifern wertvolle Informationen über Interna in der Software. Zu den Angaben, die nicht in eine Fehlermeldung gehören, zählen etwa Daten, die etwa Personen identifizieren können, Authentifizierungsbestätigungen sowie Server-Konfigurationen. Zunächst mögen die gewährten Einblicke harmlos erscheinen, wie etwa Angaben zum Installationspfad der Software. Die Vielzahl der Informationen kann eine Attacke erleichtern. Besser ist es, dem Nutzer eine einfache Fehlermeldung zuzustellen. Begründungen inklusive Log-File sind nicht angemessen.

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.

Löchrige Abwehrmechanismen

Dürftige Zugangskontrolle: Wer schon mal zu Hause eine offene Party gefeiert hat, kennt das Problem, dass Freunde ihre Freunde mitbringen. Im Haus streunen schließlich viele unbekannte Gesichter herum, und während der Gastgeber sich einzelnen Gästen zuwendet, können die Anderen ungehindert den Kühlschrank plündern oder in Schränken wühlen. Eine ungeeignete Zugangskontrolle zu Applikationen kann vergleichbare Probleme auslösen, die Konsequenzen können im schlimmsten Fall größeren finanziellen Schaden anrichten als ein geleerter Kühlschrank. Eine saubere Autorisierung ist daher Pflicht für jede Software.

Man kann Angriffen vorbeugen, indem man Daten und Funktionen sorgfältig bestimmten Benutzerrechten zuordnet. Wenn anonyme User zugelassen werden, muss auf deren Rechte besonderes Augenmerk gelegt werden.

Schwache Verschlüsselungs-Algorithmen: Kritische Daten werden üblicherweise verschlüsselt übertragen. Manch ehrgeiziger Programmierer hat es sich zur Aufgabe gemacht, eine eigene Verschlüsselungs-Routine zu schreiben, in der Hoffnung, sie könne Hacker besser abwehren. Kryptografie ist schwierig und eine eigene Wissenschaft. Wenn schon brillante Mathematiker und Wissenschaftler sich an unknackbaren Codes die Zähne ausbeißen, sollte dies Hinweis genug sein, sich nicht selbst daran zu versuchen.

Vielmehr empfiehlt sich die Verwendung einer weit verbreiteten und gestesteten Implementierung eines starken Verschlüsselungsverfahrens.

Fest programmierte Passwörter: Fest programmierte Passwörter in Authentifizierungs-Modulen sind sehr bequem etwa für Testroutinen, laden aber auch Hacker ein und führen damit jede Sicherheitsbarriere ad absurdum. Wird das Passwort öffentlich und handelt es sich dabei um dasselbe Codewort, sind alle Anwender der Software bedroht. Systemverwalter können in solchen Fällen nur wenig unternehmen, wenn das Passwort im Binärcode eingebettet ist.

Ungesicherter Zugang zu kritischen Ressourcen: Hacker sind unhöfliche Leute, denn sie fragen nicht um Erlaubnis, wenn sie sich etwas nehmen. Das tun sie gerne wenn kritische Programme, Daten und Konfigurations-Files frei zugänglich les- und veränderbar sind. Die Zugangsbeschränkung wird in der Implementierung und im Design gerne hintangestellt - und dann vergessen. Diese Sicherungslücke zu erkennen, der Aufmerksamkeit des Systemadministrators zu überlassen, ist keineswegs optimal.

Vorhersehbare Zufallsergebnisse: Benötigt ein Sicherheitssystem Zufallszahlen, um etwa eine Session-IDs oder temporäre File-Namen zu erzeugen, dann sollten sie nicht einfach vorhersehbar sein. Üblicherweise kommen dabei Pseudozufallsgeneratoren zum Einsatz (Pseudo-Random Number Generators; PRNG), dennoch ist Vorsicht geboten. Wenn Angreifer den verwendeten Algorithmus finden, können sie die folgende Zahlenkombinationen errechnen oder zumindest die Trefferwahrscheinlich verbessern.

Zufallsgeneratoren, die nicht als kryptographisch sicher bewerben werden, sind möglicherweise statische Generatoren und sollten daher nicht zum Einsatz kommen. Wer unschlüssig ist, sollte mit dem Test "FIPS 140-1" die Verlässlichkeit prüfen.

Programmausführung mit zu vielen Rechten: Programme benötige oft spezielle Rechte, um bestimmte Operationen ausführen zu können. Zu weit gefasste Zugriffsmöglichkeiten bergen Risiken. Applikationen mit Extraprivilegien können zum Teil auf Ressourcen zugreifen, die dem Nutzer der Anwendung eigentlichen verwehrt bleiben sollten. Beispielsweise kann die Software andere Programme starten und damit dem User Zugriff auf geschützte Daten einräumen. Die Vergabe von Rechten sollte daher sehr eng begrenzt sein. Vor allem der Zugang zu Betriebssystem-Ressourcen, die mit weit gefassten Rechten versehen sind, muss stark kontrolliert werden.

Verlagerung von Security-Aufgaben vom Server zum Client: Gefährlich wird es auch, dem Client Security-Aufgaben zu übertragen, die eigentlich dem Server vorbehalten sind (etwa Authentifizierung, Authentisierung, Eingabeprüfung). Hacker finden hier möglicherweise einen Angriffspunkt, indem sie einen eigenen Client entwickeln, der auf Authentifizierung verzichtet. Die Folgen sind kaum absehbar, das Ausmaß hängt mit den Aufgaben ab, die dem Client aufgebürdet wurden.

Sicherheitsroutinen gehören auf den Server, mahnt das SANS-Institut. Interaktionen und Eingaben auf dem Client sollte man nie bedingungslos vertrauen. Allenfalls können Security-Mechanismen auf dem Client zusätzlich implementiert werden.