Im ersten Teil unserer Blogserie haben wir Grundbegriffe kennengelernt und mögliche Folgen mangelnder IT-Sicherheit näher beleuchtet. Im zweiten Teil geht es um sichere Software-Entwicklung – ein Steckenpferd der MT AG. 

Eins ist klar: Wir müssen einer Rückkehr der Frickler entgegenwirken, wie Jan Mahn sie unlängst im c’t Magazin angeprangert hat, uns als Berufstand weiterentwickeln und anderen Ingenieurswissenschaften nacheifern. Dazu gehören unter anderem eine eingehende Analyse von Schadensfällen und das Beenden von unsicheren Praktiken. Warum hat es Jahrzehnte gedauert, bis eine Programmiersprache wie C auch nur ansatzweise durch eine sichere Alternative wie Rust  abgelöst wurde? Das Versagen von Software-Produkten und die damit einhergehenden Schäden werden irgendwann nicht mehr als unvorhersehbares Unglück sondern als vermeidbarer Mangel aufgefasst werden, was zur Verabschiedung von entsprechenden Regularien durch den Gesetzgeber führen dürfte. Darauf sollte sich die IT-Branche vorbereiten und in die Weiterbildung der Entwickler*innen investieren. Einen kleinen Beitrag dazu möchten wir mit dieser Artikelserie leisten.

Ein häufig gemachter Fehler bei der Entwicklung von Software ist es, die IT-Sicherheit als einen nachgelagerten Punkt zu verstehen, anstatt sie von Beginn an einzubeziehen. Je nachdem wie sich ein Projekt entwickelt, fällt die Anforderung an die Sicherheit entweder hinten über oder wird zu einem zusätzlichen Kostenfaktor. Zum Beispiel, falls bei einem vorgeschriebenen Security-Audit des Codes fundamentale Probleme aufgedeckt werden. Wird die IT-Sicherheit hingegen schon während der Entwicklung berücksichtigt, besteht diese Gefahr erst gar nicht und die Mehraufwände sind gering.

18 einfache Tipps, wie Sie sicherere Software entwickeln:

1. Vorgehensmodell und Qualitätssicherung 

Bei jedem Projekt muss ein Vorgehensmodell bestimmt werden, welches allen Beteiligten vertraut ist und eingehalten wird. Zusätzlich muss es einen Schritt zur Qualitätssicherung geben, welcher Tests zur IT-Sicherheit beinhaltet. Dabei sollte es sich um automatisierte Tests handeln, zum Beispiel ob eine Schnittstelle die Anfragen von nicht-autorisierten Benutzern ablehnt. Das V-Modell und Scrum, als jeweilige Vertreter der klassischen sowie agilen Modelle, sind hierfür bestens geeignet.

2. Versionsverwaltung

Der entwickelte Code wird einer Versionsverwaltung unterstellt. Hierbei ist besonders zu beachten, dass Änderungen nur durch autorisierte Benutzer vorgenommen werden dürfen und niemand die Historie ändern darf. Mercurial ist in diesem Kontext eine empfehlenswerte Alternative zum ebenfalls kostenlosen und populären Git. Die Handhabung ist einfacher und die Historie lässt sich standardmäßig nicht ändern. Es gibt jedoch auch zahlreiche kostenpflichtige Lösungen wie Azure DevOps Server – die eierlegende Wollmilchsau von Microsoft – oder Helix Core.

Als weitere Maßnahme sollte der Server, auf dem die Versionsverwaltung läuft, besonders gesichert sein und regelmäßig Sicherungskopien erzeugt werden, um das Risiko einer Kompromittierung zu minimieren beziehungsweise Nachvollziehbarkeit zu schaffen.

3. Werkzeuge

Es ist wichtig, dass alle am Projekt beteiligten Entwickler*innen dieselben Werkzeuge benutzen. Dies umfasst mindestens eine Kombination aus Entwicklungsumgebung, Versionsverwaltung, Compiler und Test-Framework. Heutzutage kann darunter aber auch die gesamte CI/CD-Pipeline verstanden werden, sofern vorhanden. Wenn Teams gebildet werden, die beispielsweise an getrennten Microservices mit unterschiedlichen Technologie-Stacks arbeiten, darf es natürlich Abweichungen geben. Das Ziel sollte jedoch eine größtmögliche Einheitlichkeit sein, um Alleingänge und damit verbundene Risiken zu vermeiden. Selbstverständlich sollte immer die neueste Version des jeweiligen Werkzeugs verwendet werden.

4. Umgebung 

Wir empfehlen, mehrere, gleichartige Umgebungen beziehungsweise Stages einzurichten. Bevor eine neue Version einer Software ausgerollt werden darf, muss sie diese erfolgreich durchlaufen. Es muss mindestens eine von der Produktivumgebung getrennte Testumgebung existieren. Üblicherweise gibt es daneben noch eine Entwicklungs- sowie eine Integrationsumgebung. Alle Umgebungen sind ausschließlich für den jeweiligen Zweck zu verwenden und entsprechend zu sichern. So dürfen Entwickler*innen beispielsweise keine Rechte auf der Produktivumgebung haben, in der Entwicklungsumgebung jedoch freie Hand eingeräumt bekommen. Wichtig ist, dass nur die Produktivumgebung von außen erreichbar ist.

5. Architektur und Schnittstellen 

Beim Entwurf der Architektur und der Planung der Schnittstellen sollte der Rat eines*r Expert*in für IT-Sicherheit eingeholt werden. So können grobe Schnitzer vermieden und Best Practices eingebracht werden. Sofern die Schnittstellen nicht öffentlich sind, muss dementsprechend eine Authentifizierung und Autorisierung der Benutzer erfolgen. In der Regel bieten alle Frameworks zur Entwicklung von Schnittstellen die Möglichkeit dazu an.

6. Programmiersprache 

Theoretisch ist es in allen Programmiersprachen möglich, sicheren Code zu schreiben. In der Praxis machen es einem manche jedoch einfacher als andere. Wir empfehlen Programmiersprachen, welche eine hohe Speicher- und Typ-Sicherheit aufweisen wie etwa Java, C# und Rust. Weniger empfehlenswert hingegen sind C, C++ oder PHP. Durch die eingebauten Mechanismen werden zahlreiche sicherheitsrelevante Bugs wie Buffer Overflows und Over-reads, Use-After-Free und Type Confusion vermieden. 

Für Entwickler*innen dürfte diesbezüglich der Foliensatz von Erik Poll, Dozent für Cyber Security an der Radboud-Universität Nijmegen, interessant sein. 

7. Eingabe 

Die unzureichende Überprüfung von Eingaben ist die mit Abstand häufigste Ursache für unvorhergesehene Fehler und Sicherheitslücken. Von daher gilt: jede Eingabe auf Richtigkeit und Sinnhaftigkeit überprüfen! Ein Vorname mit 14.680 Zeichen ist ebenso ungültig wie eine Kundennummer mit Buchstaben.

Neben klassischen Benutzereingaben sind alle Eingaben zu überprüfen, die über Schnittstellen oder Dateien geschehen. Wo eine syntaktische Überprüfung möglich ist, sollte diese – beispielsweise mittels regulären Ausdrücken oder vordefinierten Schemen – durchgeführt werden.

Bei der Validierung von XML-Dateien muss sichergestellt sein, dass der Parser externer Entitäten nicht auflöst, andernfalls ist eine XXE Attack möglich. Obwohl der XML-Standard schon 20 Jahre alt ist, handelt es sich dabei um einen neuartigen Angriff, welcher das Auslesen von Dateien auf dem Zielsystem beabsichtigt. 

Bei der Deserialisierung von Daten, der Umwandlung von Zeichenketten in Objekte, ist ebenfalls Vorsicht geboten. Die Forschung hat sich dabei vor allem auf Java konzentriert und bereits Auswege aufgezeigt aber auch andere Programmiersprachen wie Python oder PHP sind betroffen. Allgemein muss der zu deserialisierende Typ vom Programm fest vorgegeben werden und darf nicht von der Eingabe abhängen. Die Daten sollten aus einer vertrauenswürdigen Quelle stammen und vor Manipulation geschützt werden – dazu später mehr. 

Zusätzlich ist zu beachten, welcher Weiterverarbeitung Eingaben unterliegen, um unterschiedliche Arten von Injection Attacks zu vermeiden. Falls sie beispielsweise in eine Datenbankabfrage einfließen, ist die Verwendung von Prepared Statements unerlässlich. Grundsätzlich sollten Benutzereingaben niemals ausgeführt werden und nur nach einer sehr strengen Überprüfung als Parameter beim Aufruf anderer Programme dienen. 

8. Ausgabe 

Unter Umständen ist es sinnvoll, Ausgaben zu validieren, um keine ungültigen Daten an abhängige Prozesse weiterzugeben. Einem Angreifer dürfen im Fehlerfall keine nützlichen Informationen über den Aufbau und die Funktionsweise des Programms zukommen. So gehört etwa ein Stracktrace ins Log aber nicht in die Antwort auf eine ungültige Anfrage. Bei fehlgeschlagenen Anmeldeversuchen sollte nicht mitgeteilt werden, ob der Benutzer existiert, das Passwort falsch war oder ähnliches. 

9. Voreinstellungen 

Die Software sollte bei ihrer Auslieferung so konfiguriert sein, dass ein sicherer Betrieb möglich ist. Ein Negativbeispiel hierfür ist MongoDB, eine NoSQL-Datenbank, welche standardmäßig ohne Zugriffskontrolle ausgeliefert wird und in der Vergangenheit bereits für manchen Datenreichtum gesorgt hat.

10. Privilegien 

Jede Software sollte mit minimalen Privilegien ausführbar sein. Es gibt nur wenige Gründe, warum ein Prozess mit administrativen Rechten laufen muss. In der Produktivumgebung sollte es für jede Anwendung einen eigenen Benutzer geben, der über die benötigten Rechte verfügt. Dies macht es einem Angreifer besonders schwer, seine Rechte auf dem Zielsystem zu erhöhen, sofern er eine Sicherheitslücke gefunden hat.

11. Kryptografie 

Der Einsatz von Kryptografie ist unabdingbar und die Anwendungsfälle sind breit gefächert. Als erste Faustregel gilt: niemals eigene kryptografische Methoden entwickeln! Nur standardisierte Algorithmen sollten verwendet werden, die mittel- bis langfristig als sicher gelten. Die verwendeten Bibliotheken, welche die Algorithmen implementieren, müssen vertrauenswürdig sein. Als Kriterien dienen die Quelloffenheit, die Qualität des Codes sowie Audit-Berichte oder erlangte Zertifizierungen. Empfehlenswert sind insbesondere Bouncy Castle und Libgcrypt

Die Vertraulichkeit von Daten kann durch Verschlüsselung geschützt werden. Die Daten können von einem Angreifer nicht gelesen werden, sofern dieser keine Kenntnis über den verwendeten Schlüssel und das eingesetzte Verfahren besitzt. 

Digitale Signaturen stellen die Integrität von Daten sicher, indem die Daten zunächst in eine Hash-Funktion gegeben werden und der resultierende Hash-Wert anschließend mit einem asymmetrischen Verfahren verschlüsselt wird. Eine unbemerkte Manipulation durch einen Angreifer ist nun nicht mehr möglich. 

Die Verfügbarkeit von Daten lässt sich nicht durch kryptografische Methoden, sondern weitestgehend durch regelmäßige Backups auf externen Medien, oder alternativ die Verwendung eines Dateisystems mit Snapshot-Funktionalität wie Btrfs, erreichen. 

In einigen Anwendungsfällen ist es zweckmäßig bis notwendig, kryptografisch-sichere Zufallszahlen zu verwenden. Diese werden in der Regel vom Betriebssystem bereitgestellt. Unter unixoiden Betriebssystemen kann dazu einfach aus /dev/random gelesen werden, während unter Microsoft Windows die Funktion CryptGenRandom() der CryptoAPI verwendet werden sollte.

12. Passwörter 

Für sichere Passwörter muss eine Passwortrichtlinie definiert und angewandt werden. Die einzelnen Passwörter dürfen keinesfalls im Klartext gespeichert werden, sondern nur als Salted Hash. Dazu sollte eine geeignete Hash-Funktion wie scrypt oder Argon2 verwendet werden. Diese gehen mit einem hohen Rechenaufwand einher und machen es so für einen Angreifer unattraktiv, erbeutete Hash-Werte von Passwörtern zu knacken.

13. Undokumentierte Funktionen 

In informationstechnischen Systemen gibt es oft undokumentierte Funktionen für Fachpersonal zur Wartung oder Diagnose. Bei Geräten dient zur Autorisierung in der Regel eine Reihenfolge von bestimmten Eingaben, die oft eine zeitliche Komponente haben, und beispielsweise ein ansonsten nicht sichtbares Menü einblenden. Recherchieren Sie doch mal im Internet, wie bei Ihrem Auto die Testprozedur für das Kombi-Instrument (engl. Instrument Cluster Test) aufgerufen wird und erfreuen Sie sich an dem Schauspiel. Doch nicht immer sind derartige Funktionen harmlos. So gefährdet beispielsweise ein führender Hersteller von Netzwerkequipment regelmäßig mit undokumentierten Benutzern (und Standardpasswörtern) die IT-Sicherheit seiner Kundschaft. Es versteht sich von selbst, dass derartige Hintertüren inakzeptabel sind. Falls undokumentierte Funktionen vorhanden sind, müssen diese genauso abgesichert sein wie die dokumentierten Funktionen. 

14. Logging und Monitoring 

Alle sicherheitsrelevanten Ereignisse, wie fehlgeschlagene Anmeldeversuche, invalide Eingaben, Änderungen an Einstellungen der Software oder aufgetretene Fehler, sind zu protokollieren. Je nach Ereignis sollte eine Benachrichtigung der verantwortlichen Mitarbeiter*innen erfolgen, denn jedem erfolgreichen Angriff gehen zahlreiche gescheiterte Versuche voraus, welche es zu erkennen gilt. 

15. Code-Analyse 

Zur statischen Analyse des entwickelten Codes stehen entsprechende Werkzeuge zur Verfügung. Diese zielen darauf ab, typische Fehler zu erkennen. Leider treten immer wieder falsch-positive Treffer auf. Um diese auszusortieren, muss im Code in der Regel ein Kommentar mit einem bestimmten Format eingefügt werden – was mit entsprechendem Aufwand verbunden ist. Wenn Sie sich für einen Einsatz entscheiden, sollte das Werkzeug jedoch lokal aufgesetzt werden anstatt „nach Hause zu  telefonieren“. Andernfalls wird nicht nur der Code offengelegt, sondern die potenziellen Sicherheitslücken gleich mit. 

16. Komponenten von Dritten 

Die Verwendung von fremden Paketen in der Software-Entwicklung ist heutzutage üblich, birgt jedoch ein erhebliches Sicherheitsrisiko – auch wenn Entwickler*innen das nicht gerne hören. Neben der unbekannten und daher nicht vertrauenswürdigen Urheberschaft des Codes, der so in die Software einfließt, ist die zunehmende Abhängigkeit der öffentlich verfügbaren Pakete untereinander ein Problem. Wenn nur ein gut vernetztes Paket kompromittiert wird, kann der Schadcode auf unzählige Systeme und in noch mehr Builds gelangen. Die Pakete werden dadurch zum Teil einer quasikritischen Infrastruktur. Mit dieser Verantwortung haben viele Urheber, welche oft nur eine natürliche Person umfasst, meist nicht gerechnet

Neben der klassischen Kompromittierung ist eine weitere Angriffsmethode auf Paketmanager das so genannte Typo Squatting. Dabei werden Pakete veröffentlicht, die sich nur marginal vom Originalnamen eines Pakets unterscheiden und irrtümlich eingebunden werden. Üblicherweise ruft der Paketmanager für jedes Paket bei der Installation eine Setup-Routine auf. Diese wird mit den Rechten der aktuellen Benutzer*in ausgeführt. Ein Angreifer kann damit allerlei Schabernack treiben, da Entwickler*innen meist lokale Administratoren sind. Folglich sollten Komponenten von Dritten nicht leichtfertig eingebunden werden und zumindest einer Teamentscheidung unterliegen. Die Verantwortlichen der Paketmanager haben diese Probleme mittlerweile erkannt und bemühen sich um Abhilfe. Es ist jedoch zu erwarten, dass uns die über diesen Vektor möglichen Supply Chain Attacks in den kommenden Jahren noch häufiger beschäftigen werden.

17. Code Signing 

Beim Code Signing handelt es sich um eine in den letzten Jahren aufgekommene Methode, bei der Build-Artefakte, wie ausführbare Dateien oder Bibliotheken, digital signiert werden. Damit soll sichergestellt werden, dass die Datei vom Inhaber des Zertifikats erzeugt und nicht verändert wurde, sie also authentisch ist. Was nicht heißt, dass die so signierte Software keine Schwachstellen enthält. Es wurden auch bereits Zertifikate gestohlen, denen Microsoft Windows vertraut, und damit Schadcode signiert. Es ist eben keine Lösung, das Vertrauen in eine Anwendung allein an der vermeintlichen Urheberschaft festzumachen. 

18. Up- und Downloads 

Jedes Hoch- und Herunterladen von Dateien ist mit einem Sicherheitsrisiko verbunden. Der übermittelte Dateiname könnte unter anderem rekursive Pfade enthalten und so dafür sorgen, dass die Anwendung in anderen Verzeichnissen liest oder schreibt als geplant. Es bietet sich daher an, mit dem Hash-Wert des Dateinamens oder einer ID zu arbeiten. Beim Upload muss zudem sichergestellt werden, dass ausführ- oder interpretierbare Dateitypen abgelehnt werden. Der Dateityp muss anhand des Inhalts bestimmt werden – und nicht durch die Endung des Dateinamens. Jedes Dateiformat hat so genannte Magic Numbers, welche dies ermöglichen. 

Mit diesen Tipps sollte es ein Leichtes sein, die IT-Sicherheit in Ihren Softwareprojekten zu erhöhen. Im abschließenden Teil unserer Blogserie beschäftigen wir uns mit drei Maßnahmen zur IT-Sicherheit und zeigen Ihnen deren oft verkannte Nachteile.


IT-Sicherheit – Blogserie Teil 1:

IT-Sicherheit – Blogserie Teil 3:

Kostenlose Downloads rund um das Thema IT und Digitalisierung

Keine Kommentare

    Schreibe einen Kommentar

    Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.