Managing von Laufzeitschwachstellen

Der Umgang mit Laufzeitschwachstellen ist ein ziemlich unangenehmes Problem der modernen Softwareentwicklung. In diesem Beitrag werden wir eine Reihe von Ansätzen untersuchen, die von der Sicherheitsgemeinschaft vorgeschlagen und umgesetzt wurden. Einige Ansätze zielen darauf ab, das Vorhandensein von Fehlern zu verhindern, andere zielen darauf ab, die Fehler zu finden, bevor der Code bereitgestellt wird, und wieder andere zielen darauf ab, die Ausnutzung vorhandener Fehler in der Praxis zu erschweren.

Overview Vulnerability Management Approaches

Verhindern von Memory Corruption

Der radikalste Ansatz ist der Versuch, zu verhindern, dass Schwachstellen zur Laufzeit in die Anwendung eingeführt werden. Der Wechsel von einer speicherunsicheren Sprache wie C (bei der die Last der Speicherverwaltung dem Programmierer aufgebürdet wird) zu einer speichersicheren Sprache wie Rust oder Python verhindert von vornherein eine Reihe von Schwachstellen, die den Speicher beschädigen können. Ähnliche Effekte, wenn auch schwieriger zu beweisen, lassen sich mit der Ausbildung von Entwicklern und strengen Kodierungskonventionen erzielen. Diese Ansätze sind zwar wertvoll, helfen aber nicht bei Projekten mit bestehenden Codebases, die in speicherunsicheren Sprachen geschrieben wurden.

Finden von Fehlern vor dem Deployment

Ein zweiter Ansatz für den Umgang mit Laufzeitschwachstellen besteht darin, das wahrscheinliche Vorhandensein von Schwachstellen durch Speicherkorruption in einer Codebase zu erkennen und zu versuchen, diese vor dem Einsatz in einer Produktionsumgebung zu finden. Dies kann mit einer Reihe von Techniken erreicht werden:
  • Eine umfassende Testsuite kann nützlich sein, um sicherzustellen, dass der Code der Spezifikation entspricht. Von Menschen geschriebene Testsuiten konzentrieren sich jedoch in der Regel auf die beabsichtigte Funktionalität, während Schwachstellen per Definition unbeabsichtigt sind.
  • Automated software testing, or fuzzing, can be used to automatically construct interesting test cases and explore any reachable branch of the program. Fuzzing is a very effective technique to find bugs and we use it extensively at SANCTUARY to uncover complex bugs in our software during development. However, it does not give any guarantee about the absence of further bugs in the program. 
  • Statische Analysewerkzeuge können eine Reihe von Code-Patterns erkennen und melden, die gegen die Best Practices verstoßen und oft zu Laufzeitschwachstellen führen. Ein beliebtes Werkzeug ist der Clang Static Analyzer, der leicht zu einem LLVM-basierten Kompilierungsfluss hinzugefügt werden kann und auch ein Ansatz ist, den wir bei SANCTUARY verwenden, um eine hohe Codequalität zu gewährleisten. Während statische Analysewerkzeuge helfen, eine gute Codequalität zu gewährleisten, geben sie auch häufig falsche Warnungen aus.
  • Mit Hilfe formaler Methoden lässt sich nachweisen, dass sich das Programm entsprechend einer Spezifikation verhält. Dies ist eine sehr leistungsfähige Garantie; allerdings kann die formale Überprüfung der Korrektheit eines Programms einen erheblichen menschlichen Aufwand erfordern. Daher lässt sich diese Methode nur schlecht auf komplexe Software anwenden. Die Anwendung formaler Methoden auf älteren Code ist sogar noch schwieriger, und oft muss ein Kompromiss darüber geschlossen werden, welche Teile verifiziert werden sollen.
Die Anwendung dieser Techniken führt in der Regel zur Entdeckung vieler Bugs. Solange jedoch keine formalen Methoden verwendet werden, ist es unmöglich zu garantieren, dass die Anwendung fehlerfrei ist. Daraus folgt, dass Laufzeitverteidigungen  ebenfalls verwendet werden sollten.

Unerkannte Schwachstellen Verteidigen

Der letzte Ansatz für das Management von Laufzeitschwachstellen trägt der Tatsache Rechnung, dass es selbst nach gründlichen Tests wahrscheinlich immer noch Schwachstellen im Speicherbereich gibt, die nicht entdeckt wurden. Laufzeitverteidigungen  Diese zielen darauf ab, die Ausnutzung von Schwachstellen zu erschweren. Laufzeitverteidigungen können nach ihrem Funktionsprinzip kategorisiert werden:
  • Verteidigungen basierend auf Software Diversity (oder Randomisierung) ändern automatisch einige Details der Anwendung, die der Angreifer kennen muss, um den Angriff durchzuführen. Die Funktionalität der Anwendung bleibt unverändert, aber dem Angreifer fehlen nun einige Informationen, die für den Angriff erforderlich sind. Eine gängige Abhilfemaßnahme besteht beispielsweise darin, den Ort des Anwendungscodes im Speicher zu randomisieren. Randomisierungsschutzmaßnahmen sind konzeptionell einfach und zeichnen sich in der Regel durch gute Kompatibilität und geringen Leistungs-Overhead aus. Wenn der Angreifer jedoch in der Lage ist, das Geheimnis der Randomisierung zu erraten oder auf irgendeine Weise offenzulegen, ist die Schutzmaßnahme nicht mehr wirksam. Daher kombinieren wir bei SANCTUARY die Zufallsgenerierung mit den beiden anderen unten genannten Schutzmaßnahmen.
  • Verteidigungen basierend auf Integritätsprüfungen  instrumentieren die Anwendung, um bestimmte Sicherheitseigenschaften durchzusetzen. Ein bekanntes Beispiel ist die Kontrollflussintegrität (Control-Flow Integrity, CFI), die sicherstellt, dass der tatsächliche Kontrollfluss eines Programms mit einem Kontrollflussgraphen (Control-Flow Graph, CFG) übereinstimmt, der aus dem Quellcode des Programms generiert werden kann. Während CFI deterministische Sicherheitsgarantien bietet, ist die Generierung genauer CFGs sehr schwierig, was dazu führt, dass approximative CFGs verwendet werden müssen, was die Schutzgenauigkeit verringert. Außerdem schützt CFI nicht vor Angriffen, die nicht von der theoretischen CFG abweichen; dazu gehören einfache Angriffe, die nur einige Daten aus dem Speicher lesen, und komplexere Angriffe, die nur Daten betreffen.
  • Verteidigungen basierend auf Speicherisolation  zielen darauf ab, die Anwendung in kleinere Komponenten aufzuteilen und Barrieren zwischen ihnen zu erzwingen, so dass eine Schwachstelle in einer Komponente nicht für einen Angriff auf eine andere Komponente genutzt werden kann. So verwenden beispielsweise vertrauenswürdige Ausführungsumgebungen (Trusted Execution Environments, TEEs) eine durch Hardware verstärkte Speicherisolierung, um ihre Integrität und Vertraulichkeit zu gewährleisten. Dies ist auch das Kernkonzept der Sanctuary Embedded ConsolidationDennoch kann es immer noch eine Schwachstelle innerhalb derselben Komponente geben, für die wir bei SANCTUARY die beiden erstgenannten Abhilfemaßnahmen in Kombination mit umfangreichen Tests einsetzen.
Diese Laufzeitverteidigungen können auch kombiniert werden: So kann beispielsweise eine Verteidigung wie CFI innerhalb eines TEE eingesetzt werden. Monitoring kann ebenfalls genutzt werden um aus einem Angriff folgende Anomalien zu erkennen.
Kontaktieren Sie uns, um mehr darüber zu erfahren, wie wir Ihre Embedded-Projekte unterstützen können!

Haben Sie Fragen?

Kontaktieren Sie uns, um mehr darüber zu erfahren, wie wir Ihre Embedded-Projekte unterstützen können!