Hawlitzek GmbH/ Veröffentlichungen/ Client-Server-Security/
 

Hawlitzek IT-Consulting GmbH ©Foto Kirsten Literski-Hawlitzek

Client-Server-Security

Dieser Artikel ist im JavaSpektrum 2/99 erschienen. Vielen Dank an SIGS für die Genehmigung zur Veröffentlichung auf dieser Webseite!

Sicherheitsaspekte für Client-/Server-Szenarien

Meist hört man auf die Frage nach Sicherheit unter Java immer wieder dieselben Antworten: Sandbox und signierte Applets. Aber es gibt fast keine solchen Applets und das Thema Security umfasst viel mehr als nur diese zwei Facetten.
Dieser Artikel bezieht sich deshalb auf das gesamte Spektrum von Sicherheitsmechanismen für die Java-Anwendungsentwicklung in Client-/Server-Szenarien. Er basiert auf einer Untersuchung, die der Autor im Auftrag der Bayerischen Landesbank durchgeführt hat.

Bei Client-/Server-Architekturen gibt es drei Dinge, die vor Manipulationen und Fehlern zu schützen sind: Code und Ressourcen auf Client und Server sowie die Kommunikation zwischen den beiden (s. Abb.1).


Abb. 1: Angriffspunkte bei Client-/Server-Architekturen

Die Programmiersprache Java wurde unter besonderer Beachtung von Sicherheitsaspekten entwickelt. Die Sprache selbst und die im JDK enthaltenen Bibliotheken reduzieren dabei einen Großteil der Risiken oder stellen zumindest Mechanismen zu deren Vermeidung bereit, die man in anderen Sprachen selbst implementieren oder zukaufen müsste.
Die folgende Übersicht fasst die einzelnen Risiken zusammen und zeigt Lösungsmöglichkeiten auf, die wir uns im folgenden näher ansehen werden.

Aspekte der Client-/Server-Security

Für die drei in Abb. 1 gezeigten Angriffspunkte gibt es jeweils verschiedene Angriffsmöglichkeiten und entsprechende Schutzmechanismen:
Schutz des Clients vor

  • unbekanntem/unerwünschtem Code durch Java-Sandbox, Protection Domains (s. Kasten "Begriffe")
  • manipuliertem Code durch ClassLoader, Code-Verifier
  • fehlerhaftem Code und Programmierfehlern durch Code-Verifier, Sprachentwurf
Schutz des Servers vor
  • unberechtigten Zugriffen durch Authentifizierung, Identifizierung
  • manipulierten Clients durch dynamisches Laden von Klassen, Session Keys
  • ungültigen Versionen durch das Dienstprogramm serialver des JDK, dynamisches Laden von Klassen, RMI/CORBA
Schutz der Kommunikationsverbindung vor
  • Mithören durch Verschlüsselung
  • Verfälschen durch Verschlüsselung, digitale Signaturen
  • Ableugnen des Vorgangs oder der Identität durch digitale Signatur, Protokollierung (Audit)
Zunächst zum Schutz des Clients

Ein spezielles Problem von im Inter-/Intranet verteilten Applikationen ist, dass der Nutzer oft gar nicht damit rechnet, dass dort Gefahren durch bösartige oder fehlerhafte Anwendungen lauern könnten, da er ja nur eine Webseite anwählt. Im Gegensatz zu lokalinstallierten Programmen, die vor einer Freigabe zunächst in einer Testumgebung evaluiert werden können, starten Java-Applets sofort und automatisch. Auch eine Firewall hilft hier nichts, denn der heruntergeladene Code wird lokal ausgeführt. Man könnte nur alle Applets sperren und damit auch sämtlichen Nutzen verlieren.
Zum Glück waren die Entwickler von Java sich aber dieses Problems bewusst und haben Mechanismen zum Schutz des Clients vor unbekanntem oder gar unerwünschtem Code aus einer unsicheren, nicht vertrauenswürdigen ("untrusted") Quelle eingeführt. Jedoch zeigte sich das Prinzip der so genannten "Sandbox" als zu restriktiv und wird Schritt für Schritt weiterentwickelt:

JDK 1.0 - Sandbox

Bei den JDK-Versionen 1.0.x wurde die Sandbox eingeführt. Man ging hier von der Prämisse aus, dass jeglicher Java-Code, der auf dem lokalen Rechner installiert ist (CLASSPATH), als sicher zu betrachten ist und alles, was von außen kommt, als unsicher. Vor diesem potentiell gefährlichen Code müssen alle lokalen Daten und Ressourcen vor Manipulation und Informationsweitergabe geschützt werden. Dazu zählt insbesondere:

  • keine Zugriffe auf das Dateisystem (Dateien lesen/schreiben/löschen/umbenennen, Informationen über Dateien, Verzeichnisse oder deren Attribute ermitteln oder verändern, Verzeichnisse anlegen),
  • kein Zugriff auf Systemressourcen wie System-Zwischenablage, Drucker und Systemdaten wie Benutzer-Systemdaten (z. B. Userid),
  • keine Netzwerkverbindungen mit anderen Rechnern als dem Ursprungs-Host des Applets (also dem Webserver),
  • kein Start von Prozessen oder Programmen,
  • keine Threads außerhalb der eigenen Thread-Gruppe starten oder stoppen,
  • keine Beendigung des Laufzeit-Interpreters,
  • kein Laden dynamischer Bibliotheken (z. B. nativer C/C++-Code),
  • keine Veränderung von Sicherheitseigenschaften.

Besonderen Schutz genießen zudem die Systemklassen (z. B. java.*), die nichtüberladen bzw. ersetzt werden dürfen, um ein Ausschalten der Sicherheitsmechanismenbeispielsweise durch den Austausch der Klasse SecurityManager durch eineDummy-Implementierung zu verhindern.

JDK 1.1 - signierte Applets

Die Restriktionen der Sandbox waren sehr stark. Es war zum Beispiel nicht möglich, einen Textverabeitungs-Client als Java-Applet im Intranet zu schreiben, der Dateien lokal drucken oder speichern kann. Deshalb hat Sun bei der Version 1.1 des JDK die Möglichkeit geschaffen, besonders vertrauenswürdigen Applets zusätzliche Rechte einzuräumen.
Neben dem sicheren lokalen Code und dem unsicheren Code aus dem Internet gibt es nun auch Code aus einer vertrauenswürdigen Quelle ("trusted code"). Das kann zum Beispiel ein Webserver im Intra- oder Extranet sein. Für diesen Code kann man nun Rechte einräumen, zum Beispiel "für alle Applets, die vom Intranetserver www.our-domain.com stammen, ist Drucken erlaubt".
Es muss sichergestellt sein, dass dieser Code mit zusätzlichen Berechtigungen auch wirklich aus einer vertrauenswürdigen Quelle stammt. Zu diesem Nachweis werden alle dazu notwendigen Klassen und Ressourcen (z. B. Bilder) in eine Java-Archiv-Datei (JAR) verpackt und dieses Archiv mit Hilfe der Krypto-Klassen aus java.security und dem Dienstprogramm javakey digital signiert. Die digitale Signatur bezieht sich dabei auf die im Archiv enthaltenen Daten und es kann erkannt werden, wenn Klassen verändert oder bei der Übertragung beschädigt wurden.
Leider hat sich dieses Modell nicht durchgesetzt und zwar aus drei Gründen:

  • Es war beschränkt auf Applets.
  • Es gibt keinen etablierten und kostengünstigen Weg um Zertifikate für digitale Signaturen zu bekommen.
  • Jeder Browser-Hersteller hat eigene Wege zur Implementierung gewählt.

Insbesondere der letzte Punkt war kritisch, denn zum Beispiel ist es nicht möglich, ein browser-unabhängiges signiertes JAR im Inter-/Intranet bereitzustellen, weil Microsoft für den Internet Explorer sein proprietäres cab-Format verlangt und Netscape-eigene Security-Klassen benutzt. Die Administration der Rechte auf den Clients ist bei jedem Browser anders (z. B. HotJava, Internet Explorer, Navigator und appletviewer pflegenje eigene Policy-Datenbanken, z. B. als Policy-Datei oder in Form einer Reihe von Einträgen in der Registry).).

Java 2 - Protection Domains

Bei der neuen Java 2 Plattform (ehemals JDK 1.2) hat man diese Probleme aufgegriffen (s. auch [Kue98] ). So ist es jetzt beispielsweise möglich, die Zugriffsrechte via Policies für jeglichen Java-Code zu definieren, nicht nur für Applets aus dem Internet. Dabei kann zum Beispiel ein Administrator die Rechte unternehmensweit vorgeben, der Endanwender hat nur noch die Möglichkeit, diese weiter zu verfeinern (d. h. weiter einzuschränken, wenn der Administrator ihm dieses Recht zugesteht).
Für Zugriffe auf einzelne Ressourcen gibt es spezielle Zugriffsrechte ((Permissions). Beispiele wären Lese-/Schreibzugriff auf eine bestimmte Datei auf dem lokalen Rechner oder Netzwerkverbindungen zu einem bestimmten Rechner (Host + Portnummer). Diese Permissions werden als Zugriffskontrollisten lokal in der so genannten Policy-Datenbankgespeichert.
Man kann dies mit der Zugriffskontrolle in Netzwerkbetriebssystemen wie Windows NT oder OS/2 vergleichen, nur dass in Java die Rechte nicht einem Benutzer oder einer Benutzergruppe eingeräumt werden, sondern einem oder mehreren Websites (z. B. die "lokale Intranet-Zone" im Internet Explorer). Eine Policy gibt beispielsweise an, welches Maß an Zugriffsrechten Java-Code von einem bestimmten Ursprung (z. B. Webserver) erhält.
Um das Zertifizierungsproblem zu lösen, wird nun der Standard X.509 V3 unterstützt, mit dem über eine Hierarchie von Zertifizierungsbehörden (CAs) die Erstellung von digitalen Signaturen erleichtert wird.
Die wichtigste Erweiterung in Java 2 ist aber eine feingranulare Berechtigungsvergabe für Code aus beliebiger Quelle. Wenn der Code ausgeführt wird, wird eine abgesicherte Laufzeitumgebung erzeugt, die man als Protection Domain bezeichnet. Dieser Code kann Requests für den Zugriff auf bestimmte Ressourcen enthalten. Anhand der Quelle (URL) und den möglicherweise mitgeführten Zertifikaten wird durch Vergleich mit den lokalen Policies entschieden, ob ein Request erlaubt ist. Falls nicht, wird dem Programm dies durch eine Exception mitgeteilt.
Wie werden nun diese Schutzmechanismen durchgesetzt und wie schützt man den lokalen Rechner vor manipuliertem oder fehlerhaftem Code?

Schutzmechanismen

Der Schutz erfolgt in mehreren Schritten (Abb. 2). Schon zur Entwicklungszeit unterstützt Java durch das Sprachdesign die Entwicklung sicheren Codes. Dazu zählen zum Beispiel der Verzicht auf direkte Speichermanipulation, die strenge Typisierung und die automatische Garbage Collection. Somit werden durch Programmierfehler versehentlich “unsicher” gemachte Anwendungen, die Speicherfehler verursachen oder Memory Leaks haben, von vornherein ausgeschlossen.

Abb. 2: Kette der Schutzmechanismen

Bei der Veröffentlichung, dem Publishing, des Codes, zum Beispiel der Bereitstellung auf einem Webserver, hat man die Möglichkeit, signierte Archive zu erstellen, die die Integrität des Codes gewährleisten. Im JDK 1.1 geschieht dies mit dem Werkzeug javakey, unter Java 2 mit jarsigner.
Das nächste Glied in der Kette ist die Überprüfung des Codes auf dem Client nach dessen Übertragung (Download). Schließlich könnte der Code von einem manipulierten Compiler stammen, der obige Mechanismen umgeht, oder er könnte bei der Übertragung beschädigt worden sein.
Je nach Quelle der auszuführenden Java-class-Datei (lokal aus dem CLASSPATH oder überein Netzwerk) werden verschiedene ClassLoader aktiv.

Lokaler Start

Falls die Anwendung lokal gestartet wurde, gibt es keinerlei Sicherheitsbeschränkungen (JDK 1.0 und 1.1).
Beispiel: Alle class-Dateien liegen auf der ausführenden Maschine oder auf derengemappten/gemounteten Laufwerken und werden mit java <start-classname> oder appletviewer <html-file> aufgerufen.

Start vom Netz

Falls die Anwendung vom Netz gestartet wurde, wird zwangsläufig eine gesicherte Variante des ClassLoaders (AppletClassLoader) benutzt, der folgende Voraussetzungen für die Ausführung garantiert:

  • die auszuführende Klasse ist von java.applet.Applet abgeleitet und benutzt damit spezielle Sicherheitsmechanismen mit Hilfe so genannter SecurityManager;
  • die geladenen Klassen liegen in einem anderen Namensraum als die lokal vom System geladenen Klassen;
  • eine Bytecode-Verifikation wird durchgeführt.

Für die Erweiterungen in Java 2 wird ein SecureClassLoader benutzt, der auch bei lokalen Anwendungen benutzt werden kann.
Der ClassLoader prüft das Format der class-Dateien, ob die Vererbung korrekt ist (z. B. keine finalen Klassen abgeleitet wurden) und ob Referenzen als gültige Typen definiert sind.
Zweck des Bytecode-Verifiers ist der Schutz vor manipuliertem Java-Code, der z. B. mit einem veränderten Java-Compiler erzeugt werden könnte. Damit soll verhindert werden, dass durch ungültige Speicherzugriffe die virtuelle Maschine beschädigt wird bzw. abstürzt und kein unerlaubter Zugriff z. B. auf Systemklassen erfolgt. Der Bytecode-Verifier stellt insbesondere sicher, dass

  • die Namensräume eingehalten werden;
  • keine ungültigen Speicherzugriffe erfolgen (Array-Grenzen einhalten etc.);
  • keine unerlaubten Typkonvertierungen durchgeführt werden;
  • Stack-Überlauf vermieden wird.

Das letzte Kettenglied stellt der SecurityManager dar. Dieser existiert zum Beispiel zwangsläufig, wenn ein Applet aus dem Inter-/Intranet geladen wurde oder optional bei Applikationen. Wenn es nun einen solchen gibt, werden durch ihn zur Laufzeit alle Ressourcenzugriffe auf die Einhaltung der Policies geprüft . Falls diese verletzt werden, generiert der SecurityManager eine SecurityException.
Im verfeinerten Sicherheitskonzept von Java 2 wird der SecurityManager durch den mächtigeren AccessController ersetzt, der zum Beispiel auch bei Ressourcenzugriffen von Java-Applikationen einsetzbar ist. Die Kompatibilität zu den vorigen JDK-Versionen wird aber gewahrt, in dem es zum einen auch hier einen schlanken SecurityManager gibt, der im wesentlichen den AccessController aufruft, und zum anderen die Policies standardmäßig so vorbelegt sind, dass lokaler Code alle Rechte besitzt und aus dem Internet geladener Codewie im Sandbox-Modell beschränkt ist.

Zusammenfassung

Der Client wird geschützt vor:

  • Ressourcenzugriffen von nichtsignierten Applets, die aus dem Netz bezogen wurden;
  • unerlaubten Speicherzugriffen und Eingriffen in die Laufzeitumgebung durch manipulierte Java-Compiler und class-Dateien;
  • Ersetzen von Systemklassen und anderen Versuchen der Umgehung der Sicherheitsmechanismen;
  • Zugriffen auf jegliche Ressourcen, die nicht der definierten Policy entsprechen (seit Java 2).

Diese Einschränkungen können mit JDK 1.1 und Java 2 optional für Klassen aus trustedCode-Quellen aufgehoben werden.
Kein Schutz besteht vor:

  • manipulierten Laufzeitumgebungen;
  • fehlerhaften Implementierungen der Sicherheits-Features von Java in Browsern und anderen Laufzeitumgebungen;
  • Ressourcenzugriffen von Klassen, die lokal gestartet wurden (außer Java 2);
  • Ressourcenzugriffen von Klassen aus signierten Applets, sofern in der Laufzeitumgebung so konfiguriert.

In diesem Zusammenhang ist auch an das Sicherheitsbewußtsein der Anwender zu appellieren. Wenn ein Heimanwender in seiner Browser-Konfiguration alles erlaubt oder ein Unternehmen die Zugriffsrechte in der Browser-Konfiguration nicht oder zu unsicher vorgibt, muss man sich nicht wundern, wenn irgendein Applet etwas "zerschießt".

Nun zum Schutz des Servers

Zunächst gibt es die grundsätzliche Notwendigkeit, einen Server zu sichern, z. B. vor Ausfall oder gegen Manipulation. Diese Risiken sind jedoch nicht sprachspezifisch und daher nicht Inhalt dieses Artikels.
Angenommen, der Java-Code wird von einem Webserver heruntergeladen und als Applet ausgeführt. Der Bytecode ist nun lokal vorhanden und wird lokal ausgeführt. Dies kann zum Beispiel eine autonome Anwendung sein, die keinerlei Verbindung mehr zum Server hat. Der Webserver dient in diesem Szenario als reiner Fileserver, der die HTML-Datei sowie die notwendigen class-Dateien bereitstellt und danach nicht mehr kontaktiert wird.
Das http-Protokoll ist verbindungslos und im Internet kann der Webserver wegen des Einsatzes von Proxies (Firewalls) meist nicht einmal den Client-Rechner identifizieren, zudem das Applet heruntergeladen wurde. Somit hat der Server keine Möglichkeit, die Ausführung des Codes auf dem Client zu überwachen und den Code vor Manipulation zu bewahren.
Will man auf dem Server Dienste anbieten, die mit den Clients in Dialog stehen, hat man einen weiteres Problem: Da der Dienst über eine Schnittstelle mit dem Server in Kontakt treten soll (etwa ein Socket), muss diese freigegeben werden. Es muss verhindert werden, dass die offene Stelle nicht ungewollt benutzt wird. Damit sind nicht nur böswillig veränderte Client-Programme oder Hacking-Versuche gemeint , auch gegen fehlerhafte oder veraltete Programmversionen sollte der Server gewappnet sein.

Schutz gegen unberechtigte Zugriffe

Man schützt Server-Zugriffe üblicherweise durch Authentifizierung und Identifizierung, damit kein Unberechtigter auf den Server zugreifen kann, der unberechtigt oder in falschem Namen einen Dienst in Anspruch nehmen oder gar den Server und dessen Daten beschädigen könnte.
Dieser Schutz geschieht zum Beispiel durch Eingabe von UserID und Passwort oder durch PINs und TANs. Die Implementierung dieser Mechanismen an sich wird nicht durch Java unterstützt, es sei denn durch die Bereitstellung der kryptographischen Klassen aus java.security und Verfahren wie SSL, die wir uns im Abschnitt über den Schutz derKommunikationsverbindung noch näher ansehen werden. Eine andere Möglichkeit zur Identifizierung sind Magnetkarten oder Chipkarten.

Schutz vor manipulierten Clients und falschen Versionen

Damit der Server ausschließlich von zugelassenen Clients benutzt werden kann, sollte er den Client-Code überprüfen können. Damit will man vermeiden, dass veraltete Client-Versionen benutzt werden und dass ein manipuliertes Client-Programm geschrieben werden kann, das sich beim Verbindungsaufbau normal verhält, aber bei sicherheitskritischen Zugriffen verändert ist.
Eine Idee dazu wäre, dass der Server eine charakteristische Kennzahl ((Hash-Wert) kennt, die den Client -Code charakterisiert. Man könnte dazu die so genannte serialVersionUID verwenden, die von Java im Umfeld der Objektserialisierung verwendet wird.
Dies sollte jedoch aus zwei Gründen nicht getan werden:

  • Bei der Berechnung der serialVersionUID findet nur die öffentliche Schnittstelle der Klasse Eingang, nicht die eigentliche Implementierung. Deshalb können Manipulationen des geladenen Codes durch den Benutzer nicht erkannt werden.
  • Die serialVersionUID kann als statisches Feld angegeben werden. Das bedeutet, wenn eine Konstante (static final Feld) mit dem Namen serialVersionUID existiert, wird zur Ermittlung nicht der Hash-Algorithmus ausgeführt, sondern statt dessen die Konstante benutzt. Damit ist folgender Angriff möglich: Ermittlung des Wertes mittels serialver und Eintragung dieser Zahl als static final serialVersionUID. Danach kann die Klasse beliebig modifiziert werden, auch eine Änderung der Felder und der öffentlichen Schnittstelle bleibt dann unerkannt.

Wie oben ausgeführt, gibt es für die Server-Seite keine sinnvolle Möglichkeit, anhand von Versionskennzeichen und der Tatsache, dass der Code von einem Webserver heruntergeladen wurde, festzustellen, dass der Client-Code unmodifiziert abläuft. Die verwendeten Kommunikationsprotokolle einer Client-/Server-Applikation können zwar die übertragenen Daten sichern, stellen aber auch keinen Mechanismus zur Verifikation der Unversehrtheit des Client-Codes dar.
Benötigt wird also ein Mechanismus der Sicherheitsabfrage durch den Server. Ein solcher Mechanismus müsste gewährleisten, dass die Antwort des Clients auf dessen Code basiert (z. B. ein Digest = Hash-Wert) und zusätzlich auf einer Laufzeitinformation (SessionKey), die zwischen Client und Server vereinbart wird. Dies ist notwendig, um zu verhindern, dass am Client der Digest einmal statisch ermittelt wird und dann als fixer Wert an den Server geschickt wird, anstatt jedes Mal neu berechnet zu werden. Wenn möglich, sollten die in Java enthaltenen Klassen und Mechanismen genutzt werden.
Es gibt nun verschiedene Vorschläge für einen solchen Mechanismus der Sicherheitsabfrage durch den Server:

  • Vereinbarung von Session Keys,
  • dynamisches Laden von generischen Klassen,
  • Code-Verwürfelung,
  • Verlagerung des kritischen Codes auf den Server.
Vereinbarung von Session Keys

Zwischen Client und Server werden Session Keys vereinbart (z. B. lange Zufallszahlen) und übertragen. Basierend auf diesem Wert wird über die in der Package java.security enthaltenen Algorithmen ein Hash -Wert berechnet, der über die gesamte Klasse gebildet wird und damit die Integrität der Client-Klassen sicherstellt.
Offene Punkte dabei sind:

  • Vereinbarung und Übertragung der Session Keys;
  • Einbindung des Session Keys in die Generierung des Hash-Wertes dergestalt, dass der Algorithmus nicht über einen vorab berechneten Hash-Wert über die Klassen überlistet werden kann;
  • Ermittlung des Hash-Wertes aus der Klasse heraus, das heißt es wäre ein Zugriff der Klasse auf ihren eigenen Bytecode nötig.
Dynamisches Laden von generischen Klassen

Die abstrakte Klasse ClassLoader definiert Methoden zum dynamischen Laden und Generieren von Klassen (zur Laufzeit). Somit kann der AppletClassLoader oder ein eigener ClassLoader die sicherheitskritischen Klassen erst zur Laufzeit vom Server bekommen. Wenn ein Server-Prozess diese Klassen dynamisch generiert und Informationen zum Aufbau der Klassen übers Netz lädt, können sie zum Client übertragen und dort dynamisch mit defineClass und resolveClass erzeugt werden.
Natürlich sollten auch diese Klassen mit Session-Information versehen werden, damit ein Angreifer nicht durch einmaliges Verfolgen dieses Prozesses die Generierung simulieren kann.

Lokale Manipulationsmöglichkeiten von heruntergeladenem Code

Im Falle von Applets wird Java-Bytecode von Webbrowsern geladen und bis zu deren Beendigung im Speicher gehalten. Die Implementierung ist herstellerabhängig, zum Beispiel könnten die Klassen lokal in temp-Verzeichnissen auf der Festplatte abgelegt werden und wären damit zugänglich. Selbst wenn die Klassen nur im Speicher liegen würden, könnte sie ein geschickter Hacker durch einen Speicherabzug oder über die Auslagerungsdatei rekonstruieren.
Aus dem Java-Bytecode kann dann Quellcode mittels eines Java-Decompilers erstellt werden (Reverse Engineering, siehe Kasten "Manipulation von heruntergeladenem Code"). Dies stellt eine ideale Ausgangsbasis zur Programmierung eines veränderten Clients dar. Die generierten Quellen entsprechen zwar nicht den ursprünglichen (Kommentare fehlen, Variablenbezeichnungen können teilweise nicht rekonstruiert werden etc.), sind aber für den geübten Programmierer recht leicht lesbar.
Es gibt allerdings eine Reihe von Werkzeugen, so genannte Code-Verwürfler, die den Sinn haben, Java-Bytecode so neu zu strukturieren (to obfuscate), dass es schwierig ist, mittels Decompiler eine brauchbare Quelle zu erhalten (siehe Kasten "Manipulation von heruntergeladenem Code"). Insbesondere werden alle Variablen- und Methodenbezeichner durch nicht-sprechende Namen ersetzt, soweit das möglich ist.
Die meisten dieser Werkzeuge sind im Einsatz jedoch sehr problematisch. Oft ist der entstandene Code nur noch auf bestimmten Plattformen einsetzbar bzw. nur mit bestimmten Browsern ausführbar, manchmal muss man plattformspezifische Installationen auf den Clients durchführen. Der entstandene Bytecode ist in Entwicklungsumgebungen nicht mehr importierbar. Dies liegt daran, dass die Werkzeuge versuchen, alle Informationen zu vernichten, die nicht zum Ablauf, das heißt zur Laufzeit, nötig sind. Entwicklungsumgebungen wie Visual Age for Java brauchen aber diese sogenannten Reflection-Infos, um die Klassen/Beans nutzen zu können.
Der Einsatz von Code-Verwürflern nach der Entwicklungszeit ist dann zwar denkbar, es müssten aber wohl aufwendige Evaluierungen der Zielplattformen durchgeführt werden.

Verlagerung der kritischen Funktionalität auf den Server

Eine andere Variante zur Verminderung der Angriffsfläche auf dem Client besteht darin, dort nur minimale Funktionalität ablaufen zu lassen und alle kritischen Code-Teile auf dem Server zu halten. Dies ist der sprichwörtliche Thin Client, der nur Oberfläche darstellt und einfache Validierungen durchführt. In Java bieten sich dafür Mechanismen wie Servlets, RMI oder CORBA an.
Bei Servlets läuft der gesamte Java-Code auf dem Server ab, der Client ist HTML-basiert. Man benutzt hier beispielsweise HTML-Forms für Benutzereingaben und dynamisch vom Servlet generierte HTML-Seiten für die Ausgabe. Diese Art der Client-/Server-Architektur ist sehr sicher, lässt aber nur eine primitive Oberfläche am Client zu und verlagert viel Last auf den Server.
RMI und CORBA hingegen benutzen auch Java-Code am Client. Die kritischen Code-Teile (Methoden oder Dienste) werden aber am Server ausgeführt. Die Programmierung der Clients ist relativ einfach, da sie weitgehend so geschrieben werden können, als ob es sich um eine autonome Anwendungen mit lokalen Objekten handeln würde. Die Aufrufe der eigentlich am Server befindlichen Objekte werden über Stubs oder Proxies dorthin weitergeleitet.
RMI ist einfacher zu programmieren, aber auf reine Java-Lösungen beschränkt, CORBA hingegen ist sprachübergreifend und in der Regel performanter.

Zusammenfassung

Reine Client- oder Server-Lösungen (Applets bzw. Servlets) sind für den Server relativ sicher, kritisch ist die Etablierung einer Applikationsschnittstelle. Der Server sollte dann durch Authentifizierung und Identifizierung geschützt werden. Der Server sollte zudem durch Session Keys und Zentralisierung des kritischen Codes vor unzulässigem Client-Code gesichert werden. Der verbleibende Client-Code sollte durch Code-Verwürfler vor Decompilierung geschützt werden.

Schutz der Kommunikationsverbindung

Falls eine Client-Server-Kommunikation ausgeführt wird, erfolgt diese über normale Kommunikationsprotokolle wie TCP/IP Port/Socket-Kommunikation, Datagramme, http oder IIOP. Diese Protokolle haben an sich recht wenig mit Java zu tun und bieten in der Regel ihre eigenen Sicherheitsmechanismen an. In [Vogl98] finden Sie ein Beispiel.
Eine interessante Alternative ist auch der Einsatz von Standard-Protokollen wie IIOP, die durch ein gesichertes Protokoll (HTTPS) getunnelt werden, da dafür erstens der Entwickler die Verschlüsselung nicht selbst programmieren muss und zweitens die Kommunikation auch über Firewalls hinweg funktioniert.

Verschlüsselung durch Security-Klassen

Die Verschlüsselung und Schlüsselgenerierung erfolgt in Java mittels ,PublicKey"-Verfahren nach der Java Cryptographic Architecture (JCA). Dies geschieht mit Klassen und Schnittstellen des Pakets java.security. Hier sind jedoch nur die Schnittstellen für die Benutzung der Verfahren festgelegt, nicht jedoch das Verfahren (die Implementierung) selbst. Es ist lediglich definiert, dass Einweg-Hash-Funktionen Verwendung finden. Diese produzieren einen Ausgabe-String fester Länge (Hash oderDigest). Man nennt diese Algorithmen deshalb auch MessageDigest-Algorithmen.
Diejenigen Klassen, die die abstrakten Klassen MessageDigest, KeyPairGenerator undSignature implementieren, nennt man Engine-Klassen. Sun liefert MD5 und SHA-1 alsMessageDigest-Algorithmen und DSA als Signatur-Algorithmus.
APIs für Ver- und Entschlüsselung werden zusammen mit einigen Implementierungen in einem externen Paket namens JCE (Java Cryptography Extension) ausgeliefert, das jedoch aus export-rechtlichen Gründen nicht außerhalb der USA eingesetzt werden darf.

Arten des Schutzes

Die Kommunikationsverbindung muss vor verschiedenen Angriffsmöglichkeiten gesichert werden. Vor dem Mithören und vor dem unberechtigten Zugriff schützt die Verschlüsselung der Daten. Vor dem Abfangen und der verfälschten Weitergabe schützen digitale Signaturen ebenso wie vor der Ableugung eines Vorganges beziehungsweise einer Identität.
Für diese Anwendungsfelder sind die oben beschriebenen Klassen geeignet. Weiterhin sollten Übertragungsprotokolle aller Kommunikationen oder wenigstens stichprobenartig in Form eines Audit protokolliert werden, um sie bei Bedarf analysieren zu können.

Fazit

Dieser Artikel hat gezeigt, was bei der Entwicklung sicherer Anwendungen zu bedenken ist. Die Ursache vieler Risiken liegt in der Offenheit von Internet-Anwendungen,Client-/Server- und Multi-Tier -Architekturen, nicht an der Sprache Java. Ganz im Gegenteil. Java liefert für eine Vielzahl von Problemen fertige Lösungen mit (im Gegensatz zu anderen Technologien wie ActiveX im Internet). Es ist an den Entwicklern, diese richtig einzusetzen.

Literatur

[Kue98] R. Kuehnel, Tutorial über die Programmierung von Java-Applets, 12. Teil, JavaSpektrum, November/Dezember 1998

[Vogl98] W. Vogl, Java und ,Secure Socket Layer", JavaSpektrum, September/Oktober1998

Begriffe

CA

Certification Authority, Zertifizierungsbehörde, die die Echtheit von Zertifikaten bestätigt

digitale Signatur

ein für die Daten charakteristischer "Fußabdruck" wird bei "Public-Key"-Verfahren zur Unterzeichnung verwendet und kann mit dem öffentlichen Schlüssel überprüft werden.

JAR

Java ARchive, komprimierter Satz von Klassen und ressourcen, möglicherweise mit Zertifikat

JCA

Java Cryptographic Architecture, abstrakter Satz von Krypto-Klassen und -Schnittstellen für verfahrensunabhängige Anwendung von Kryptologie in Java

JCE

Java Cryptography Extension, exportbeschränktes kryptographisches Zusatzpaket des JDKs

Policy

unternehmensweite oder lokale Definition von Rechten mittels Zugriffskontrollisten für Code aus bestimmter Quelle und/oder mit Zertifikaten

Protection Domain

abgesicherte Laufzeitumgebung für Code aus einer bestimmten Quelle (URL), der durch lokale Policies bestimmte Rechte zugewiesen sind

Public Key Verfahren

asymmetrische Verschlüsselungsmethode mit einem privaten (geheimen) und einem öffentlichen Schlüssel, z.B. RSA

Security Manager

durch den AppletClassLoader instantiiertes Objekt, das die Befolgung der Policies überwacht, im Java 2 ersetzt durch AccessController

Zertifikat

Bestätigung der Identität eines Unternehmens oder einer Person durch einen vertrauenswürdigen Dritten, gesichert durch eine digitale Signatur, z.B. X.509 V3

zurück zum Text

Manipulation von herunter geladenem Code

Java-Decompiler

JavaDis von Wingsoft (www.wingsoft.com)
OEW for Java/Deja Vu (www.isg.de/)
Mocha (www.brouhaha.com/~eric/computers/mocha.html)
IceBreaker (Website inaktiv)
weitere (GuavaD, IceBreaker, Jad, Jasmine, Jive, Decaf, SourceAgain)

Code-Verwürfler

OuyaSafe (www.wingsoft.com)
Crema (Website inaktiv)
DashO (www.preemptive.com/products.html)
JShrink (www.e-t.com/jshrink.html)
JZipper (www.vegatech.net/JZipper)
Obfuscate (Website inaktiv)

zurück zum Text

 

© 2006 Hawlitzek IT-Consulting GmbH,
Marketing&Design Kirsten Literski-Hawlitzek

Seitenanfang

Hawlitzek GmbH
Die Gründer
Dienstleistungen
Java Downloads
Vorträge
Veröffentlichungen
Kontakt
International
Sitemap