November 2017

Band 33, Nummer 11

Sicherheit: Schutz von Daten und Anwendungen vor unbefugter Offenlegung und Verwendung

Von Joe Sewell

„Sicherheitsverletzung der Daten“. Dieses drei Wörter sind wohl die furchteinflößendsten Wörter in der Softwareentwicklung. Bei Software dreht sich alles um Daten: ihre Verarbeitung, Verwaltung und Sicherung. Wenn ein Angreifer eine Sicherheitsverletzung der Daten bewirken kann, kann Ihr Unternehmen vertrauliche Informationen verlieren, die für Ihren Erfolg entscheidend sind, Albträume zu Haftungsrisiken erleben und wertvollen Markenrespekt sowie Kundenloyalität einbüßen.

Um dieses Risiko in den Griff zu bekommen und Vorschriften wie HIPAA und GDPR einzuhalten, werden kluge Entwickler und Betriebsteams Sicherheitskontrollen für Datenbanken und Webdienste implementieren. Zu diesen Kontrollen gehören End-to-End-Verschlüsselung, striktes Identitätsmanagement und Erkennung von Anomalien im Netzwerkverhalten. Im Falle eines Sicherheitsvorfalls reagieren viele solcher Kontrollen aktiv, indem sie Ereignisdetails aufzeichnen, Warnungen senden und verdächtige Anforderungen blockieren.

Diese und andere bewährte Methoden können zwar Ihre Serverkomponenten schützen, sie sind aber für Clientsoftware weniger geeignet. Damit Ihre Daten sinnvoll verwendet werden können, müssen sie in irgendeiner Form für berechtigte Benutzer und Software zugänglich gemacht werden. Wie können Sie aber sicher sein, dass auch Ihre Clientsoftware keine Datenpanne verursacht?

Viele Unternehmen erstellen beispielsweise Software, die speziell für ihre Mitarbeiter entwickelt wurde und oft auf sensible Unternehmensdaten zugreift. Und während Microsoft .NET Framework die Entwicklung von Branchenanwendungen in Sprachen wie C# oder Visual Basic .NET einfach macht, enthalten diese kompilierten Anwendungen dennoch Metadaten der höchsten Stufe sowie Zwischencode. Dies macht es einem Angreifer leicht, die App durch die nicht autorisierte Verwendung eines Debuggers zu manipulieren oder die App zurückzuentwickeln (Reverse Engineering) und eine kompromittierte Version zu erstellen. Beide Szenarien können zu einer Sicherheitsverletzung der Daten führen, auch wenn die Serverkomponenten absolut sicher sind.

Es gibt zwar einige Maßnahmen, die Sie ergreifen können, um sich gegen diese Angriffe zu schützen (z. B. Authenticode-Signatur und Codeobfuskation, um nur zwei zu nennen). Die meisten von ihnen sind jedoch passiv, da sie Angriffe lediglich verhindern, anstatt sie zu erkennen, zu melden und darauf zu reagieren. Seit Kurzem ermöglichen Ihnen neue Features in Visual Studio jedoch, Bedrohungserkennung, Berichterstellung und Reaktionsmöglichkeiten in Ihre .NET-Apps zu integrieren. Dabei ist wenig bis keine zusätzliche Codierung erforderlich. Diese Laufzeitüberprüfungen sind eine aktive Schutzmaßnahme, mit der Sie das Verhalten Ihrer App als Reaktion auf eine Sicherheitsbedrohung ändern und damit Ihre sensiblen Daten schützen können.

In diesem Artikel stelle ich am Beispiel einer typischen Branchenanwendung eine Methodik für den effektiven Einsatz von Laufzeitüberprüfungen vor. Ich werde untersuchen, wie ein Angreifer einen Debugger verwenden kann, um eine Sicherheitsverletzung der Daten zu verursachen, wie Laufzeitüberprüfungen vor dieser Sicherheitsverletzung schützen können und welche Rolle diese Steuerelemente in einem mehrschichtigen Schutzansatz spielen.

Beispiellösung

Um zu erklären, wie eine Sicherheitsverletzung in einem Client passieren kann, habe ich eine LOB-Beispiellösung (Line of Business) vorbereitet, die von bit.ly/2yiT2eY heruntergeladen werden kann. Anweisungen zum Erstellen, Ausführen und Verwenden der verschiedenen Teile der Lösung finden Sie in der Infodatei zum Beispielcode.

Die Lösung besteht aus vier Komponenten:

AdventureWorks2014-Datenbank: Dies ist eine Microsoft SQL Server-Datenbank, die das Adventure Works 2014 OLTP-­Datenbankbeispiel enthält. Sie können dieses Beispiel von bit.ly/2wM0yy0 herunterladen.

Adventure Works-Vertriebsdienst: Hierbei handelt es sich um einen ASP.NET-Webdienst, der Kundendaten aus der Datenbank bereitstellt, z. B. auch sensible Daten wie etwa Kreditkarteninformationen. Um die Einrichtung dieser Komponente zu vereinfachen, verzichtet der Beispielcode auf die meisten Sicherheitskontrollen. Für die Zwecke dieses Artikels werde ich aber davon ausgehen, dass der Dienst die folgenden Funktionen implementiert:

  • Zwei Stufen der Authentifizierung: ein Benutzerkennwort und eine SMS, die bei der Anmeldung an das Telefon des Benutzers gesendet wird.
  • Zeitlich begrenzte Sitzungen.
  • SSL-Verschlüsselung für alle Anforderungen und Antworten.

Adventure Works-Vertriebsclient: Dies ist ein WPF-Desktopclient (Windows Presentation Foundation), der eine Verbindung mit dem Vertriebsdienst herstellt, um die Kundendaten zu manipulieren. Dies ist die Komponente, mit der sich der Artikel am meisten beschäftigen wird.

Wenn ein Vertriebsmitarbeiter den Client ausführt, meldet er sich über den „LoginDialog“ an, der die authentifizierte Sitzung startet und das „CustomerWindow“ öffnet. In diesem Fenster kann der Mitarbeiter Kundennamen anzeigen und bearbeiten oder das „EMailWindow“, „PhoneWindow“ oder „CreditCardWindow“ öffnen, um die sensiblen Daten eines Kunden zu bearbeiten. Einige allgemeine Funktionen werden auch in einer statischen Klasse namens „Utilities“ zur Verfügung gestellt.

Application Insights: Der Kundendienst und der Client können Nutzungs- und Fehlertelemetriedaten an Application Insights senden, auch wenn die Anwendung nicht zur Ausführung des Beispiels erforderlich ist. Mit den Laufzeitüberprüfungen, die in diesem Artikel behandelt werden, beinhalten die Telemetriedaten des Clients auch die Meldung von Schadensberichten.

Für diesen Artikel konzentriere ich mich auf die Sicherung des Vertriebsclients. Ich gehe davon aus, dass die Datenbank und der Vertriebsdienst bereits gesichert sind. Natürlich wäre das keine sichere Annahme in einem echten Szenario, aber sie hilft, einen Punkt zu zeigen: Auch wenn Sie bei der Serversicherheit „alles richtig machen“, sind Sicherheitsverletzungen durch Clientsoftware möglich.

Außerdem werde ich Kundennamen als nicht sensible Daten behandeln und mich stattdessen auf die Sicherung von E-Mail-Adressen, Telefonnummern und Kreditkarteninformationen konzentrieren. In einem echten Szenario würden auch Kundennamen als sensibel angesehen, und die nicht sensiblen Daten könnten Daten wie Adressen von Einzelhandelsgeschäften enthalten.

Sicherheitsverletzung mit einem Debugger

Debugger sind wunderbare Entwicklungswerkzeuge. Sie ermöglichen es Ihnen, kritische Logikfehler aufzudecken, knifflige Kontrollflussszenarien zu durchlaufen und Absturzabbilder zu diagnostizieren. Wie jedes Tool können Debugger aber auch in böswilliger Absicht verwendet werden.

Angenommen, das Adventure Works-Intranet wird durch einen Angreifer bedroht: vielleicht durch einen rachsüchtigen Mitarbeiter, einen Auftragnehmer außerhalb des Unternehmens oder sogar einen externen Angreifer, der sich unbefugten Zugriff auf das Intranet verschafft hat. Dieser Angreifer besitzt keinen Zugriff auf die Datenbank oder den Vertriebsdienst, kann aber auf den Laptop eines Vertriebsmitarbeiters zugreifen. Natürlich ist dies ein Sicherheitsproblem. Da aber der Vertriebsdienst zweistufige Authentifizierung implementiert hat und der Angreifer keinen Zugriff auf das Telefon des Mitarbeiters besitzt, sollten die Kundendaten sicher sein, oder?

Tatsächlich ist dies nicht der Fall. Der Angreifer kann warten, bis sich der Vertriebsmitarbeiter über den Vertriebsclient angemeldet hat und dann entweder manuell oder über ein Skript einen Debugger an den Clientprozess anfügen. Da es sich bei dem Client um eine .NET-App handelt, wird der Debugger eine Vielzahl von Informationen auf hoher Ebene offenlegen (z. B. das Sitzungstoken), auch wenn keine Debugsymbole (z. B. PDB-Dateien) vorhanden sind.

Abbildung 1 zeigt dieses Szenario. Mithilfe des WinDbg-Debuggers (bit.ly/2sh4clf) mit der Psscor4-Erweiterung (bit.ly/2hbG2kk) habe ich verschiedene .NET-Objekte im Arbeitsspeicher eines aktuell ausgeführten Vertriebsclientprozesses gesichert. Ich habe letztlich das AuthToken-Objekt gefunden und den Wert seiner HashField-Eigenschaft gesichert.

Offenlegung eines Sitzungstokens im Vertriebsclient durch WinDbg

Abbildung 1: Offenlegung eines Sitzungstokens im Vertriebsclient durch WinDbg

Mit diesem Sitzungstoken kann der Angreifer authentifizierte Anforderungen an den Vertriebsdienst im Namen des Mitarbeiters vornehmen. Der Angreifer muss den Client nicht weiter debuggen oder manipulieren. Sobald er über das Sitzungstoken verfügt, kann er direkt zum Webdienst navigieren und das Token verwenden, um eine Sicherheitsverletzung zu verursachen.

Es gibt andere Szenarien, in denen Angreifer den Client auf nicht autorisierte Weise verwenden können:

Direktes Manipulieren sensibler Daten: Während das vorhergehende Szenario ein Session Hijacking-Angriff war, weil der Vertriebsclient im Rahmen seines normalen Betriebs auf sensible Daten (z. B. Kreditkarteninformationen) zugreift, können diese Daten auch mit einem Debugger eingesehen werden. Angreifer könnten sogar bewirken, dass sich die App ungewöhnlich verhält, oder sie können die Daten in der Datenbank ändern.

Reverse Engineering: Angreifer könnten den Client auch selbst ausführen und einen Debugger anfügen, um herauszufinden, wie der Client funktioniert. In Kombination mit der einfachen Dekompilierung von .NET-Apps können Angreifer möglicherweise Exploits oder andere wichtige Details zum Client oder Dienst ermitteln, die ihnen bei der Planung eines Angriffs helfen.

Manipulation: Wenn Angreifer die Anwendung zurückentwickeln (Reverse Engineering) und auf das Dateisystem des Mitarbeiters zugreifen können, können sie den legitimen Client durch einen geänderten Client ersetzen, der heimlich Daten extrahiert oder manipuliert, wenn sich der Mitarbeiter anmeldet.

Andere Apps können auf andere Weise anfällig für Debugger sein. Beispielsweise könnte eine App, die den Standort eines Mitarbeiters zur Verfolgung des Außendiensts meldet, so manipuliert werden, dass sie ungenaue Daten bereitstellt. Oder ein Spiel könnte wichtige strategische Informationen in einem Debugger offenlegen.

Informationen zu Laufzeitüberprüfungen

Laufzeitüberprüfungen sind ein neues Feature in PreEmptive Protection – Dotfuscator Community Edition (CE), einem Schutztool, das seit 2003 in Visual Studio enthalten ist (bit.ly/2wB0b9g). Sie wissen vielleicht, dass Dotfuscator CE den Zwischencode von .NET-Assemblys verschleiern kann, aber Obfuscation ist nicht das Thema dieses Artikels. Stattdessen werde ich zeigen, wie ich Laufzeitüberprüfungen (im Folgenden nur als „Überprüfungen“ bezeichnet) verwendet habe, um es dem Vertriebsclient zu ermöglichen, sich während der Ausführung zu schützen.

Überprüfungen sind vordefinierte Validierungen, die Dotfuscator in Ihre .NET-Anwendungen integrieren kann. Ihre Apps können dann nicht autorisierte Anwendungen wie Debuggen oder Manipulation erkennen. Auch wenn ihr Namen es nicht vermuten lässt, erkennen Überprüfungen nicht nur diese Zustände, sondern können auch auf vordefinierte Weise reagieren, indem sie die App z. B. beenden. Überprüfungen können auch Anwendungscode aufrufen, sodass benutzerdefiniertes Verhalten basierend auf dem Ergebnis der Überprüfung möglich ist. Diese Berichts- und Antwortfunktionen sind pro Überprüfung konfigurierbar, sodass alle Ihre Apps auf dieselbe Weise nicht autorisierte Verwendungen erkennen können. Jede App kann aber auf diese Erkennung unterschiedlich reagieren.

Das Codebeispiel enthält eine Konfigurationsdatei „Dotfuscator.xml“, die Dotfuscator anweisen kann, den Vertriebsclient vor unbefugtem Debuggen und vor Manipulation zu schützen. Im Rest dieses Artikels werde ich erläutern, wie ich diese Konfiguration erstellt habe, welche Entscheidungen ich getroffen habe und wie Sie Dotfuscator ähnlich konfigurieren können, um Ihre eigenen Apps zu schützen.

Einrichten der Tools

Der einfachste Weg, um mit Dotfuscator CE zu beginnen, ist die Verwendung des Visual Studio-Schnellstarts (STRG+Q), um nach „dotfuscator“ zu suchen. Wenn Dotfuscator nicht installiert ist, wird eine Option zur Installation von „PreEmptive Protection – Dotfuscator“ angezeigt. Wählen Sie diese Option aus, und bestätigen Sie die entsprechenden Dialogfelder.

Sobald Dotfuscator installiert ist, stellt die Wiederholung dieser Suche eine Option zum Starten von „Extras“ > „PreEmptive Protection – Dotfuscator“ bereit. Wählen Sie diese Option aus, um mit der Verwendung von Dotfuscator zu beginnen. Nach einigen typischen Dialogfeldern bei der Erstbenutzung wird die Dotfuscator CE-Benutzeroberfläche geöffnet.

Wichtiger Hinweis: Der hier beschriebene und im Beispiel enthaltene Schutz erfordert mindestens Version 5.32 von Dotfuscator CE. Sie können ermitteln, welche Version Sie installiert haben, indem Sie „Hilfe“ > „Info“ auswählen. Wenn Sie eine frühere Version verwenden, laden Sie die neueste Version der Community Edition von bit.ly/2fuUeow herunter.

Dotfuscator arbeitet mit speziellen Konfigurationsdateien, die angeben, welche Assemblys geschützt werden sollen und wie der Schutz angewendet werden soll. Dotfuscator wird mit einer geladenen neuen Konfigurationsdatei gestartet. Ich habe diese für den Vertriebsclient mit den folgenden Schritten angepasst:

  1. Zuerst habe ich die neue Konfigurationsdatei als „AdventureWorksSalesClient\Dotfuscator.xml“ gespeichert.
  2. Dann habe ich Dotfuscator informiert, wo die Assemblys des Clients zu finden sind. Ich bin zum Bildschirm für die Dotfuscator-Eingaben gewechselt und habe auf das grüne Pluszeichensymbol geklickt. Im Dialogfeld „Select Input“ (Eingabe auswählen) bin ich zum Verzeichnis „AdventureWorksSalesClient\bin\Release“ navigiert und habe dann auf „Open“ (Öffnen) geklickt, ohne eine Datei auszuwählen.
  3. Dotfuscator hat das gesamte Verzeichnis als Eingabe mit dem Namen „Release“ hinzugefügt. Ich habe den Baumknoten erweitert, um zu überprüfen, ob die Assembly „AdventureWorksSalesClient.exe“ vorhanden ist.
  4. Dann habe ich die Konfigurationsdatei portabel gemacht, damit sie nicht spezifisch für absolute Pfade in meiner Umgebung ist. Ich habe den Knoten „Release“ ausgewählt, auf das Bleistiftsymbol geklickt und den absoluten Pfad durch „${configdir}\bin\Release“ ersetzt. „${configdir}“ ist ein Dotfuscator-Makro, das das Verzeichnis darstellt, in dem sich die Konfigurationsdatei befindet.
  5. Da sich dieser Artikel nicht mit den Codeobfuskationsfeatures von Dotfuscator befasst, habe ich diese schließlich deaktiviert, indem ich mit der rechten Maustaste auf das Element „Renaming“ (Umbenennen) in der Dotfuscator-Navigationsliste geklickt und „Enable“ (Aktivieren) deaktiviert habe.

Konfigurieren der Überprüfungsinjektion

Dotfuscator ermöglicht es Ihnen, Überprüfungen auf der Registerkarte „Checks“ (Überprüfungen) auf dem Bildschirm „Injection“ (Injektion) zu konfigurieren. Welche Auswahl Sie für die Konfiguration treffen, hängt jedoch davon ab, welche Art von App Sie schützen. Anstatt alle Features und Einstellungen aufzulisten, stelle ich schrittweise die Auswahl und Konfiguration vor, die ich für das Vertriebsclientbeispiel vorgenommen habe.

Für das Beispiel habe ich drei Überprüfungen konfiguriert:

  • Zwei Debugüberprüfungen, die die unbefugte Verwendung eines Debuggers erkennen:
    • Eine Debugüberprüfung, die die Anmeldung betrifft, um die oben beschriebenen Session Hijacking-Szenarien zu erkennen.
    • Eine Debugüberprüfung, die Abfragen betrifft, um zu erkennen, wenn ein Debugger verwendet wird, um sensible Daten im Client zu lesen/schreiben.
  • Eine Manipulationsüberprüfung, die die Verwendung einer geänderten Anwendungsbinärdatei erkennt.

Abbildung 2 zeigt die Überprüfungen aus der Vogelperspektive sowie den App-Code, mit dem sie interagieren. In den nächsten drei Abschnitten werde ich den Zweck und die Konfiguration der einzelnen Überprüfungen erläutern.

Der Vertriebsclient mit injizierten Laufzeitüberprüfungen

Abbildung 2: Der Vertriebsclient mit injizierten Laufzeitüberprüfungen

Konfigurieren der Debugüberprüfung zur Anmeldung

Die erste Debugüberprüfung bezieht sich auf das Session Hijacking-Szenario. Sie erkennt, ob während des Authentifizierungsvorgangs ein Debugger vorhanden ist, und benachrichtigt die App, wenn dies der Fall sein sollte. Die App meldet den Vorfall an Application Insights und generiert später auf nicht intuitive Weise Fehler.

Ich habe diese Überprüfung hinzugefügt, indem ich auf die Schaltfläche „Add Debugging Check“ (Debugüberprüfung hinzufügen) geklickt und so ein neues Konfigurationsfenster geöffnet habe. Abbildung 3 zeigt, wie ich diese Überprüfung konfiguriert habe.

Konfiguration für die Debugüberprüfung zur Anmeldung

Abbildung 3: Konfiguration für die Debugüberprüfung zur Anmeldung

Position: Zunächst habe ich ausgewählt, wo im App-Code die Überprüfung ausgeführt werden soll. Da diese Überprüfung Debugaktivitäten bei der Anmeldung des Benutzers erkennen soll, habe ich die Methode „LoginDialog.ConfirmLogin“ aus dem Baum „Locations“ (Positionen) ausgewählt.

Beachten Sie, dass die Überprüfung nur ausgeführt wird, wenn die entsprechende Position aufgerufen wird. Wenn ein Angreifer später einen Debugger anfügt, erkennt diese Überprüfung dies nicht. Ich werde dieses Problem aber später mit der Debugüberprüfung zur Abfrage beheben.

Anwendungsbenachrichtigung: Nachdem eine Überprüfung ausgeführt wurde, kann der App-Code benachrichtigt werden, damit die App auf angepasste Weise Berichterstellung ausführen und reagieren kann. Das Codeelement, das diese Benachrichtigung empfängt, wird als Anwendungsbenachrichtigungssenke bezeichnet und über die folgenden Eigenschaften der Überprüfung konfiguriert:

  • ApplicationNotificationSinkElement: Der Typ des Codeelements (Feld, Methode usw.).
  • ApplicationNotificationSinkName: Der Name des Codeelements.
  • ApplicationNotificationSinkOwner: Der Typ, der das Codeelement definiert.

Für alle drei Überprüfungen habe ich dieses Feature verwendet, um Vorfälle an Application Insights zu melden. Außerdem habe ich für diese Überprüfung entschieden, dass Dotfuscator eine benutzerdefinierte Antwort anstelle einer Standardantwort injizieren soll (die die anderen Überprüfungen verwenden werden). Meine Antwort lässt die Anmeldung erfolgreich verlaufen, führt aber kurz darauf zu einem Absturz der App. Durch das Trennen von Erkennung und Antwort mache ich es für einen Angreifer schwieriger, die Kontrolle zu erkennen und zu umgehen.

Damit diese Antwort erfolgt, habe ich der Klasse „LoginDialog“ ein boolesches Feld („isDebugged“) hinzugefügt und es als Senke der Überprüfung konfiguriert. Wenn die Überprüfung ausgeführt wird (die Anwendung also „LoginDialog.ConfirmLogin“ aufruft), wird das Ergebnis der Debuggererkennung in diesem Feld gespeichert: TRUE für einen erkannten Debugger, andernfalls FALSE.

Beachten Sie, dass der Zugriff auf die Senke von der Position der Überprüfung aus möglich sein muss und dass für sie Schreibberechtigungen erforderlich sind. Da die Position und die Senke Instanzmember der LoginDialog-Klasse sind, wird diese Regel erfüllt.

Im nächsten Schritt habe ich „LoginDialog.RunUserSession“ so geändert, dass dieses Feld an den CustomerWindow-Konstruktor übergeben wird:

// In LoginDialog class
private void RunUserSession(AuthToken authToken) 
{
  // ...
  var customerWindow = new Windows.CustomerWindow(clients, isDebugged);
  // ...
}

Dann habe ich den CustomerWindow-Konstruktor sein eigenes Feld („CustomerWindow.isDebugged“) festlegen und eine Meldung des Vorfalls an Application Insights vornehmen lassen:

// In CustomerWindow class
public CustomerWindow(Clients clients, bool isDebugged)
{
  // ...
  this.isDebugged = isDebugged;
  if (isDebugged)
  {
    // ClientAppInsights is a static class holding the Application 
    // Insights telemetry client
    ClientAppInsights.TelemetryClient.TrackEvent(
      "Debugger Detected at Login");
  }
  // ...
}

Schließlich habe ich noch Code hinzugefügt, der dieses Feld in verschiedene Ereignishandler liest. Zum Beispiel:

// In CustomerWindow class
private void FilterButton_OnClick(object sender, RoutedEventArgs e)
{
  // ...
  if (isDebugged) { throw new InvalidCastException(); }
  // ...
}

Ich werde die Offensichtlichkeit des Feldnamens „isDebugged“ später in diesem Artikel erläutern.

Konfigurieren der Debugüberprüfung zur Abfrage

Da ein Debugger während seiner Ausführung jederzeit an den Client angefügt werden kann, reicht die Debugüberprüfung zur Anmeldung alleine nicht aus. Die Debugüberprüfung zur Abfrage füllt diese Lücke, indem sie nach einem Debugger sucht, wenn die App sensible Daten abfragen soll, z. B. Kreditkartennummern. Die Vertraulichkeit dieser Daten bedeutet auch, dass ich es mir nicht leisten kann, die Erkennung, die Berichterstellung und die Antwort wie bei der Debugüberprüfung zur Anmeldung zu trennen, da ein Angreifer die Daten dann kennen würde. Stattdessen wird die Debugüberprüfung zur Abfrage den Vorfall melden und die Anwendung dann sofort beenden, wenn ein Debugger erkannt wird.

Ich habe die zweite Debugüberprüfung auf die gleiche Weise wie die erste Überprüfung hinzugefügt, aber dieses Mal habe ich die Überprüfung wie in Abbildung 4 gezeigt konfiguriert.

Konfiguration für die Debugüberprüfung zur Abfrage mit der Position „CreditCardWindow.UpdateData“ – andere Positionen werden nicht gezeigt

Abbildung 4: Konfiguration für die Debugüberprüfung zur Abfrage mit der Position „CreditCardWindow.UpdateData“ – andere Positionen werden nicht gezeigt

Positionen: Es gibt drei Arten von sensiblen Daten in meinem Szenario: E-Mail-Adressen, Telefonnummern und Kreditkarteninformationen. Glücklicherweise können mehrere Positionen für eine Überprüfung ausgewählt werden. In diesem Fall sind diese Positionen „EmailWindow.UpdateData“, „PhoneWindow.UpdatePhones“ und „CreditCardWindow.Update­Data“. Die Überprüfung wird immer dann ausgeführt, wenn eine dieser Positionen aufgerufen wird. Dies bedeutet, dass ich nur eine Gruppe von Überprüfungseigenschaften für alle drei Arten von sensiblen Daten konfigurieren muss.

Anwendungsbenachrichtigung: Wenn mehrere Positionen vorhanden sind, ändert sich die Funktionsweise der Anwendungsbenachrichtigungssenke. In der Debugüberprüfung zur Anmeldung konnte ich das Feld „LoginDialog.isDebugged“ als Senke angeben, weil auf dieses Feld von der einzigen Position der Überprüfung „LoginDialog.ConfirmLogin“ aus zugegriffen werden konnte. Nun muss aber jede Position in der Lage sein, auf eine Senke zuzugreifen.

Wenn die Application-NotificationSinkOwner-Eigenschaft leer ist, verwendet die Senke standardmäßig den Typ, der die Position der Überprüfung definiert. Da diese Überprüfung mehrere Positionen verwendet, ist die Senke je nach der Position unterschiedlich, an der die Überprüfung ausgelöst wurde. In diesem Fall habe ich diese Eigenschaft leer gelassen und die anderen ApplicationNotificationSink-Eigenschaften auf eine Methode namens „ReportDebugging“ festgelegt.

Werfen wir einen Blick auf die EmailWindow.ReportDebugging-Methode:

// In EmailWindow class
private void ReportDebugging(bool isDebugging)
{
  if (isDebugging)
  {
    ClientAppInsights.TelemetryClient.TrackEvent(
      "Debugger Detected when Querying Sensitive Data",
      new Dictionary<string, string> { { "Query", "Email Addresses" } });
    ClientAppInsights.Shutdown();
  }

Wenn die App die Methode „EmailWindow.UpdateData“ aufruft, wird die Überprüfung ausgeführt und dann diese ReportDebugging-Methode mit dem Argument TRUE aufgerufen, wenn ein Debugvorgang erkannt wurde. Andernfalls wird FALSE verwendet.

Das Gleiche geschieht, wenn der App-Code „PhoneWindow.UpdatePhones“ oder „CreditCardWindow.UpdateData“ aufruft. Die Methode, die durch die Überprüfung aufgerufen wird, wird in diesem Fall jedoch von „PhoneWindow“ bzw. „CreditCardWindow“ definiert. Diese Methoden werden etwas unterschiedlich implementiert, aber sie alle heißen „ReportDebugging“, nehmen ein einzelnes boolesches Argument an und geben keinen Wert zurück.

Aktion: Um die App zu schließen, wenn ein Debugger angefügt ist, lege ich die Action-Eigenschaft auf „Exit“ fest. So wird Dotfuscator angewiesen, Code zu injizieren, der die App schließt, wenn die Überprüfung einen nicht autorisierten Zustand erkennt. Beachten Sie, dass die Überprüfung diese Aktion nach der Benachrichtigung der Anwendung ausführt, sodass in diesem Szenario der Schadensbericht gesendet würde, bevor die App geschlossen wird.

Konfigurieren der Manipulationsüberprüfung

Im letzten Schritt habe ich eine Manipulationsüberprüfung hinzugefügt, um das Reverse Engineering-Szenario zu berücksichtigen. Ich habe auf die Schaltfläche „Add Tamper Check“ (Manipulationsüberprüfung hinzufügen) geklickt, um eine neue Manipulationsüberprüfung zu konfigurieren. Abbildung 5 zeigt dies.

Konfiguration für die Manipulationsüberprüfung mit der Position „LoginDialog.ConfirmLogin“ – andere Positionen werden nicht gezeigt

Abbildung 5: Konfiguration für die Manipulationsüberprüfung mit der Position „LoginDialog.ConfirmLogin“ – andere Positionen werden nicht gezeigt

Positionen: Ebenso wie bei der Debugüberprüfung zur Abfrage habe ich für die Manipulationsüberprüfung auch mehrere Positionen vorgesehen: „LoginDialog.ConfirmLogin“, „CustomerWindow.UpdateData“ und „Utilities.ShowAndHandleDialog“.

Bei einer Debugüberprüfung ist es wichtig, mehrere Positionen vorzusehen, weil der Debugger während der Ausführung jederzeit angefügt werden kann. Aber eine Manipulationsüberprüfung führt bei einer Ausführung der App nur zu einem Ergebnis: Die Laufzeit hat eine Assembly geladen, die geändert wurde, oder dies war nicht der Fall. Sollte eine Position nicht ausreichend sein? Da diese Überprüfung dazu dient, manipulierte Binärdateien zu verhindern, muss ich tatsächlich ein Szenario berücksichtigen, in dem der Angreifer in der Lage war, die Manipulationsüberprüfung selbst von einer Position zu entfernen. Durch das Vorhandensein mehrerer Positionen ist die Anwendung besser vor Manipulationen geschützt.

Vielleicht ist Ihnen aufgefallen, dass eine der Positionen („LoginDialog.ConfirmLogin“) mit der Position für die Debugüberprüfung zur Anmeldung identisch ist. Dotfuscator ermöglicht es, mehrere Überprüfungen mit verschiedenen Typen an der gleichen Position zu injizieren. In diesem Fall werden nach der Anmeldung des Benutzers sowohl Debug- als auch Manipulationsvorgänge überprüft.

Anwendungsbenachrichtigung: Für die Anwendungsbenachrichtigungssenke habe ich entschieden, dass es in diesem Fall besser wäre, nur eine einzige Senke für alle Positionen zu verwenden. Dies liegt daran, dass es mir im Gegensatz zur Debugüberprüfung zur Abfrage auf der Berichtsseite nicht wirklich wichtig ist, welche Position die Überprüfung auslöst.

Ich habe die Utilities.ReportTampering-Methode als die Senke definiert. Da der Kontext der einzelnen Positionen unterschiedlich ist, musste ich die Senke als statisch deklarieren und sicherstellen, dass sie von jeder Position aus erreichbar war. Die Methode wird folgendermaßen definiert:

// In Utilities static class
internal static void ReportTampering(bool isTampered)
{
  if (isTampered)
  {
    ClientAppInsights.TelemetryClient.TrackEvent(“Tampering Detected”);
    ClientAppInsights.Shutdown();
  }
}

Wann immer eine der Positionen der Überprüfung aufgerufen wird, ermittelt die Überprüfung, ob sie geändert wurde, seit Dotfuscator sie verarbeitet hat. Dann wird die „Methode ReportTampering“ mit einem Parameter TRUE aufgerufen, wenn eine Änderung erkannt wurde. Andernfalls gilt FALSE.

Aktion: Wenn die App geändert wurde, ist es gefährlich, den Vorgang fortzusetzen. Ich habe die Aktion der Überprüfung als „Exit“ konfiguriert, sodass die App geschlossen wird, wenn eine Manipulation erkannt wird.

Injizieren der Überprüfungen

Da die Überprüfungen nun konfiguriert sind, kann Dotfuscator diese in die App injizieren. Öffnen Sie zu diesem Zweck aus Dotfuscator CE die Konfigurationsdatei „AdventureWorksSalesClient\Dotfuscator.xml“, und klicken Sie dann auf die Schaltfläche „Build“ (Erstellen).

Dotfuscator verarbeitet die Assembly des Clients, injiziert die Überprüfungen und schreibt den geschützten Client in „AdventureWorksSalesClient\Dotfuscated\Release“. Die ungeschützte App verbleibt in „AdventureWorksSalesClient\bin\Release“.

Testen der Überprüfungen

Wie bei jeder Sicherheitskontrolle ist es wichtig, das Verhalten der App zu testen, wenn die Kontrolle eingeführt wird.

Normale Szenarien: Die Überprüfungen sollten keine Auswirkungen auf legitime Nutzer der App besitzen. Ich habe den geschützten Client normal ausgeführt und keine unerwarteten Abstürze, Anwendungsbeendigungen oder Application Insights-Ereignisse gesehen.

Nicht autorisierte Szenarien: Sie sollten auch überprüfen, ob Überprüfungen die Aktionen ausführen, die Sie erwarten, wenn die Anwendung nicht autorisiert verwendet wird. In der INFODATEI des Beispielcodes werden detaillierte Anweisungen zum Testen der Debugüberprüfungen und der Manipulationsüberprüfung aufgeführt. Um die Debugüberprüfung zur Abfrage zu testen, habe ich den geschützten Client z. B. mehrmals ausgeführt und WinDbg an verschiedenen Stellen an den Prozess angefügt. Die App hat das Vorhandensein eines Debuggers richtig gemeldet und gemäß der Konfiguration der Überprüfung reagiert.

Mehrschichtige Schutzstrategie

Die Verwendung nur einer Schutzmaßnahme reicht für die meisten praktischen Fälle nicht aus, und Überprüfungen stellen keine Ausnahme dar. Überprüfungen sollten nur eine Schicht Ihrer Schutzstrategie darstellen, zusammen mit Techniken wie End-to-End-Verschlüsselung, Authenticode-Assemblysignaturen usw. Wenn Sie mehrere Schutzschichten verwenden, können die Stärken einer Schicht die Schwächen einer anderen Schicht ausgleichen.

Im Beispiel dieses Artikels weisen einige Anwendungsbenachrichtigungssenken, die von den Überprüfungen verwendet werden, Namen wie „isDebugged“ oder „ReportTampering“ auf. Diese Namen verbleiben in kompilierten .NET Assemblys, und ein Angreifer könnte die Absicht dieser Codeelemente leicht verstehen und sie umgehen. Um dies zu vermeiden, kann Dotfuscator neben der Injektion von Überprüfungen auch eine Umbenennungsobfuskation für Ihre Assemblys ausführen. Details finden Sie in der Dokumentation zu „PreEmptive Protection – Dotfuscator Community Edition“(bit.ly/2y9oxYX).

Zusammenfassung

In diesem Artikel wurden Laufzeitüberprüfungen und einige der Probleme vorgestellt, die sie lösen. Anhand einer LOB-App habe ich gezeigt, wie eine Sicherheitsverletzung in Clientsoftware auftreten kann und wie Laufzeitüberprüfungen so konfiguriert werden können, dass sie einen solche Sicherheitsverletzung erkennen, melden und darauf reagieren.

Während in diesem Artikel die kostenlose Dotfuscator Community Edition behandelt wurde, können diese Konzepte auch auf die kommerziell lizenzierte Professional Edition übertragen werden, die zusätzliche Features für Überprüfungen enthält (bit.ly/2xgEZcs). Sie können Überprüfungen auch in Java- und Android-Apps injizieren, indem Sie das Schwesterprodukt von Dotfuscator (PreEmptive Protection – DashO) verwenden (bit.ly/2ffHTrN).


Joe Sewell arbeitet als Software Engineer und Technical Writer im Dotfuscator-Team bei PreEmptive Solutions.

Unser Dank gilt dem folgenden technischen Experten bei Microsoft für die Durchsicht dieses Artikels: Dustin Campbell


Diesen Artikel im MSDN Magazine-Forum diskutieren