Berufsakademie Stuttgart – Außenstelle Horb Studiengang Informationstechnik Semesterarbeit Software Engeneering Sicherheit in Java Student: Thomas Fischer Habichtstraße 8 71083 Herrenberg Studienjahrgang: 2003 Jan Bogner Ganserstrasse 1 72160 Horb a. Neckar Studienjahrgang: 2003 1 INHALT 1 Einleitung .............................................................................................................................3 2 Sicherheitsaspekte beim Sprachdesign ...............................................................................4 3 Sandbox ................................................................................................................................5 3.1 Entwicklung des Sandbox models ...............................................................................5 3.1 Entwicklung des Sandbox models ...............................................................................6 3.2 Bestandteile der Sandbox............................................................................................8 4 Der Class Loader..................................................................................................................8 4.1 Der Class Loader und Namespaces .............................................................................8 4.2 Class Loader und Protection Domains ........................................................................9 4.3 Trusted Class Libraries.................................................................................................9 5 Der Class File Verifier.......................................................................................................11 5.1 Die 4 Phasen des Class File Verifier .........................................................................12 6 Der Sicherheitsmanager.....................................................................................................13 6.1 Der Sicherheitsmanager bei Applets .........................................................................14 6.2 Aktivierung des Sicherheitsmanagers .......................................................................16 7 Rechteverwaltung...............................................................................................................17 8 Signing ................................................................................................................................19 8.1 Erstellen eines Signierten Applets .............................................................................20 9 Zusammenfassung..............................................................................................................21 10 Literaturverzeichnis .........................................................................................................21 2 Abstract: Das Java-Sicherheitsmodell bietet im Laufe seiner Entwicklung einen immer besser werdenden Schutz. Dies war auch notwendig in Anbetracht der zunehmenden Vernetzung unserer Welt und der ansteigenden Nutzung von Java-Applikation im Web. Dinge wie Signierung und Rechteverwaltung spielen gerade in der vernetzten Welt eine immer größer werdende Rolle. Die Mittel, die Java dazu bietet, sind zwar nicht vielfältig, aber dennoch ein wichtiges Instrument auf dem Weg zu mehr Schutz. In der vorliegenden Arbeit wird das Sicherheitskonzept von Java durchleuchtet und näher gebracht. 1 Einleitung Java ist eine Programmiersprache, die aufgrund ihrer Plattformunabhängigkeit einen hohen Stellenwert in der Softwareentwicklung eingenommen hat. Bevor Java entwickelt wurde gab es nur die Möglichkeit Programme auf den verschiedenen Systemen zu kompilieren und anzupassen, diese auch lauffähig waren. Mit Java wurde dieses Problem gelöst. Java stellt zwei Haupteinsatzgebiete bereit. Einmal die lokale Softwareentwicklung, wie auch zweitens die Entwicklung von Software für Internetanwendungen, was als Applet bezeichnet wird. Es sind viele Verbesserungen im Gegensatz zu anderen Programmiersprachen in Java implementiert worden. Dazu zählen unter anderem der Garbage Collector, welcher sich um die Zerstörung von Objekten kümmert, wenn diese nicht mehr gebraucht werden und die Plattformunabhängigkeit, welche wohl das Hauptaugenmerk bei der Entwicklung gewesen ist. Leider erkaufte man sich die Vorteile auch mit Nachteilen wie zum Beispiel Performanceeinbußen. Sicherheit sollte in Java auch eine wichtige Rolle spielen, denn gerade durch den Einsatz als Internettechnologie stellte sich die Herausforderung nach Schutzmechanismen, welche in der vorliegenden Arbeit erläutert werden sollen. 3 2 Sicherheitsaspekte beim Sprachdesign Bei der Entwicklung von Java wurde starker Wert auf die Sicherheit gelegt. Die Syntax ist zwar in weiten Teilen an C++ angelegt, beim Sicherheitskonzept versuchte man jedoch aus den Fehlern von C++ und anderen populären Sprachen zu lernen. Wichtige Sicherheitsaspekte des Java-Designs sind die Verwendung von Zugriffskontrollen für Variablen und Methoden in Klassen, die strenge Typisierung, der Verzicht von Pointern als Datentyp, der Garbage Collector (automatische Speicher-Freigabe) und die Verwendung von Paketen (packages) mit eindeutigen Namensräumen (namespaces). Java bietet, ähnlich wie C++, die Möglichkeit der Kontrolle des Zugriffs auf Methoden und Variablen eines Objekts. Die Java Bibliothek besitzt beispielsweise die Definition eines File-Objektes. Dieses File-Objekt hat eine public Methode (kann von jedem aufgerufen werden) zum Lesen und eine private Methode (kann nur vom Objekt selber aufgerufen werden) zum Lesen. Die public read-Methode führt, wenn sie aufgerufen wird, zuerst einen Sicherheitscheck durch und ruft anschließend die private readMethode auf. Java gewährleistet somit, dass nicht vertrauenswürdiger Code trotzdem sicher auf das File-Objekt zugreifen kann, indem es nur Zugriff auf public Methoden gewährleistet. Eine zweite Möglichkeit der sicheren Zugriffskontrolle ist die Fähigkeit, eine Methode oder Klasse als final zu deklarieren. Dies verhindert, dass ein Programmierer Subklassen einer kritischen Bibliothek anlegt oder Methoden einer Klasse überschreibt. Somit kann man garantieren, dass bestimmte Teile des Objekt-Verhaltens nicht manipuliert wurden. Die Programmiersprache Java ist des Weiteren so entworfen, dass Sie typsicher ist. Das bedeutet, dass der Compilerzeit-Typ und der Laufzeit-Typ auf jeden Fall kompatibel sind. Dadurch kann garantiert werden, dass Casts (Typenumwandlungen) überprüft werden, entweder zur Kompilierzeit oder zur Laufzeit, um Kompatibilität und Gültigkeit sicherzustellen. Um auf das File-Objekt-Beispiel von eben zurückzukommen: Es wird verhindert, dass ein bösartiger Programmierer ein File-Objekt als MyFiler-Typ castet, welcher den gleichen Aufbau hat, bei dem aber alle Methoden public sind. Ein weiteres Sicherheits- Feature ist der Verzicht auf Pointer als Datentyp. Dies bedeutet, dass Pointer nicht direkt vom Programmcode manipuliert werden können (Pointer Arithmetik). Dies verhindert sowohl versehentlichen als auch bösartige PointerFehler (z.B.: über den Bereich eines Arrays hinaus schreiben). Um auf das File-Beispiel zurückzukommen: Dies verhindert, dass bösartiger Code einfach direkt auf die private read-Methode zugreift, indem es Pointer Arithmetik verwendet ausgehend vom FileObjekt Pointer. 4 Java verwendet eine Garbage Collection, um nicht mehr benutzten Speicher freizugeben, anstatt es dem User zu überlassen Speicher explizit freizugeben. Dies verhindert nicht nur eine Reihe von weit verbreiteten Programmierfehlern, sondern stopft auch potentielle Sicherheitslöcher. Wenn Java manuelle Speicher-Freigabe erlauben würde, würde dies eine Hintertür für ungültige Casts öffnen. Das bösartige Programm legt zuerst ein Objekt vom Typ MyFile an und gibt anschließend den Speicher auf das Objekt frei, behält jedoch den Pointer. Danach wird umgehend ein File-Objekt angelegt, welches die gleiche Größe wie das MyFile-Objekt hat. Wenn dies richtig gemacht wird (mit einem guten Verständnis wie Speicher Allokation und Freigabe funktioniert) ist der Pointer zu dem File-Objekt der gleiche wie der zu dem MyFile-Objekt. Der bösartige Code hat nun über den MyFile-Pointer Zugriff auf die privaten Methoden des FileObjekts. Zuletzt benutzt Java Pakete (packages) um Namensraum-Kapselung zu gewährleisten. Für die Sicherheit ist dies von Bedeutung, da somit einfach zwischen runtergeladenem Code und lokalem Code unterschieden werden kann. Insbesondere kann somit verhindert werden, dass sich runtergeladener bösartiger Code als System-Bibliothek ausgibt. Wenn auf eine Klasse zugegriffen werden soll, wird diese zuerst lokal und anschließend im namespace der referenzierten Klasse gesucht. Dies garantiert auch, dass lokale Klassen nicht versehentlich runtergeladene Klassen referenzieren. 3 Sandbox Die Sandbox (dt. Sandkasten) ist ein geschützter Bereich bzw. eine Reihe von Regeln, an die sich Programmierer von Applets halten müssen. Da ein Java Applet automatisch als Teil einer html-Seite gesendet wird und sofort ausgeführt werden kann sobald es übertragen wurden, könnte es bei Systemvollzugriff großen Schaden anrichten. Dies kann sowohl versehentlich als auch bösartigerweise absichtlich geschehen. Die Sandbox Einschränkungen regeln genau, auf welche Ressourcen das Applet zugreifen kann. Der Programmierer muss also Code schreiben, welcher nur in der Sandbox „spielt“, ähnlich einem Kind, dem es erlaubt ist alles zu machen, was es will innerhalb des eingerahmten Bereichs eines realen Sandkastens. Die Sandbox kann also als kleiner Bereich im Computer gesehen werden, in welchem der Code das Applets „spielen“ kann, aber es ist ihm untersagt irgendwoanders „rumzuspielen“. Abbildung 1: Kind spielt im Sandkasten (Sandbox) 5 3.1 Entwicklung des Sandbox models Im ursprünglichen Sandbox Sicherheitsmodell des JDK 1.0 wurde eine sehr eingeschränkte Umgebung bereitgestellt für unsicheren Code aus offenen Netzwerken. In dem Sandboxmodell der folgenden Grafik wird lokaler Code als vertrauenswürdig angesehen und hat vollen Systemzugriff, wohingegen runtergeladener „Remote Code“ (Applet) nicht vertrauenswürdig ist und somit nur auf einem beschränkten Teil der Systemressourcen zugreifen kann, welche ihm die Sandbox zur Verfügung stellt. Ein Security Manager ist in diesem Schichten-Modell verantwortlich festzustellen, welcher Ressourcenzugriff erlaubt ist. Abbildung 2: Sicherheitsmodell JDK 1.0 Es war Applets unter anderem untersagt auf die Platte zuzugreifen, einen Prozess zu starten, eine neue DLL zu laden oder Systemeingenschaften einzusehen. Das Applet stellte somit kein Sicherheitsrisiko da. Jedoch sah man ein, dass diese Einschränkungen zu restriktiv waren. Durch den stark eingeschränkten Systemzugriff ließen sich viele Anwendungen nicht als Applet realisieren. JDK 1.1 führte deshalb das Prinzip der signierten Applets („signed applets“) ein. Signierte Applets werden mit ihrer Signatur als signierte JAR (Java Archives)-Dateien versendet. Somit ist es möglich zwischen Applets von vertrauenswürdigen Quellen und welchen aus nicht vertrauenswürdigen Quellen zu unterscheiden. Vertrauenswürdigen Applets wurde nun voller Systemzugriff gewährt, wohingegen nicht vertrauenswürdige Applets weiterhin nur innerhalb der Sandbox arbeiten konnten. Dieses Sicherheitsmodell war jedoch zu eingeschränkt, da es nur die vorgegebene Sandboxumgebung oder Vollzugriff erlaubte. 6 Abbildung 3: Sicherheitsmodell JDK 1.1 JDK 1.2 führt eine Reihe von Verbesserungen gegenüber JDK 1.1 ein. Es ist in 1.2 nun möglich, sowohl lokalen als auch runtergeladenen Code in verschiedene Sicherheitsstufen einzuteilen, indem man Code definierte Rechte (permissions) zuweist. Diese definieren jeweils Zugriffserlaubnisse auf einzelne Ressourcen, wie zum Beispiel der Lese- /Schreibzugriff auf bestimmte Dateien oder Verzeichnisse oder die Möglichkeit, auf einen Host oder Port zuzugreifen. Das Laufzeitsystem organisiert den Code in einzelnen Domains, welche jeweils einen Satz von Klassen mit denselben Zugriffserlaubnissen zusammenfassen. Eine Domain kann zu einer Art Sandbox konfiguriert werden, somit kann ein Applet weiterhin in einem eingeschränkten Umfeld laufen. Normale Anwendungen laufen uneingeschränkt, wie zuvor auch, können aber bei Bedarf einer bestimmten Sicherheitsstufe zugewiesen werden. Abbildung 4: Sicherheitsmodell JDK 1.2 7 3.2 Bestandteile der Sandbox Das Sicherheitsmodell von Java umfasst den gesamten Umfang der Java Architektur. Würde es Teile geben, welche nicht mit in das Sicherheitskonzept integriert sind, könnten diese Schwachstellen bilden. Solche Schwachstellen könnten dann ausgeforscht werden und es wäre möglich außerhalb der Sandbox zu „spielen“. Die Hauptbestandteile der Sandbox sind: - der Class Loader - der Class File Verifier - der Sicherheitsmanager Diese werden in den folgenden Kapiteln näher beschrieben. 4 Der Class Loader 4.1 Der Class Loader und Namespaces Die erste Stelle, an der der Class Loader eine wichtige Rolle für die Sicherheit spielt, sind die Namensräume (namespaces). Der vollständige Name einer Java Klasse beinhaltet oft den Namen des packages, zu dem es gehört, so gibt es beispielsweise keine Standardklasse String in der Java API, aber es gibt eine Klasse java.lang.String. Auf der anderen Seite, ist es nicht zwingend notwendig, dass eine Klasse zu einem package gehört. In diesem Fall ist der vollständige Name einfach der Name der Klasse. Diese Klassen befinden sich in einem default package, wobei zu beachten ist, dass es ein eigenes default package für jeden Class Loader gibt, welcher von der virtuellen Maschine verwendet wird. Schaut man sich beispielsweise an was passiert, wenn man auf www.mercedes.de geht und ein Applet lädt, welches eine Klasse Auto (ohne einen package Name) verwendet. Danach surft man zu www.audi.de und es wird dort ein weiteres Applet geladen, welches ebenfalls eine Klasse Auto (wieder ohne package Namen) aufruft. Dies sind natürlich zwei unterschiedliche Klassen, aber der Name, mit dem sie aufgerufen werde, ist der gleiche. Wie schafft es die virtuelle Maschine, diese beiden Klassen auseinander zu halten? Die Antwort zu dieser Frage liegt in der internen Arbeitsweise des Class Loaders. Wenn eine Klasse vom Class Loader geladen wird, wird sie in einer Referenz innerhalb dieses Class Loaders geladen. Ein Class Loader in Java ist einfach ein Objekt, dessen Typ eine Klasse ist, welche von der ClassLoader Klasse abgeleitet wurde. Wenn die Virtuelle Maschine Zugriff auf eine bestimmte Klasse braucht, ruft sie einfach den entsprechenden Class Loader auf. Wenn die Virtuelle Maschine beispielsweise Code von www.mercedes.de ausführt und Zugriff auf die Auto Klasse benötigt, ruft sie einfach den Class Loader auf, welcher das Applet von www.mercedes.de geladen hat, damit dieser ihr die Klasse zur Verfügung stellt. Damit dies auch mit der Auto Klasse von www.audi.de funktioniert, muss diese Klasse von einem anderen Class Loader geladen werden als der von www.mercedes.de. 8 Die Unterscheidung von Klassen, die von unterschiedlichen Class Loadern geladen werden, findet statt - egal welche Pakete betroffen sind. Wenn beispielsweise Audi die Klasse de.luxus.Auto definieren würde und Mercedes ebenfalles eine Klasse mit dem Namen de.luxus.Auto definieren würde, so würden diese Klassen weiterhin von unterschiedlichen Class Loader Instanzen geladen. In Java haben Klassen, welche zu demselben package gehören, bestimmte Privilegien. Sie können auf alle Klassen des Paketes zugreifen, welche default sind (nicht public, private oder proteced). Zusätzlich können sie auch auf alle default Instanz Variablen zugreifen. Wenn es die zuvor beschriebene Namespace Trennung nicht gebe, könnte beispielsweise www.evilcar.de de.luxus.EvilStuff definieren. Diese könnte dann auf die default Variablen von de.luxus.Auto zugreifen und beispielsweise die Bremsen manipulieren. 4.2 Class Loader und Protection Domains Ein weiterer wichtiger Ansatzpunkt für die Sicherheit in Java sind die Protection Domains. Wird ein Klasse geladen, so wird ihr von ihrem Class Loader eine Protection Domain zugewiesen. Diese Protection Domain verwaltet alle Rechte, welche der Klasse gewährt werden. Sie bestimmt also, zu welchen Systemressourcen sie Zugriff hat (Dateien, Netzwerkzugriff...). 4.3 Trusted Class Libraries Trusted class libraries sind die Pakete, die von der Java Virtual Machine als definitiv sicher angesehen werden. Dazu gehören die Klassen der Core Java API. In den meisten älteren VM Implementierungen war der eingebaute Standard (Primordial) Class Loader für das Laden lokal verfügbarer Klassen verantwortlich. Seit Java Version 1.2 bilden die Class Loader eine Hierarchie. Die Class Loader wurden in einer VaterSohn Beziehung angeordnet. Der so genannte Bootstrap Class Loader steht dabei in der Hierarchie an der Spitze. Dieser Class Loader ist nur für das Laden der Klassen der Core-Java API zuständig. Für das Laden anderer Klassen, wie z.B. die Klassen der ausgeführten Applikation, sind seit Version 1.2 benutzerdefinierte Class Loader verantwortlich. Wird eine Version 1.2 Virtuelle Maschine gestartet, werden also einer oder mehrere benutzerdefinierte Class Loader gestartet (abhängig von der Java Plattform Implementierung). Am unteren Ende dieser Kette ist der Default System Class Loader. Der Name System Class Loader bezeichnet üblicherweise den benutzerdefinierten Class Loader, der die erste Klasse der Applikation lädt. 9 Möchte also eine Applikation eine Klasse laden, so gibt der System Class Loader die Anfrage an seinen Vaterknoten weiter, der selbst die Anfrage wieder an seinen Vaterknoten weiterleitet. Der letzte Knoten in der Hierarchie ist der Bootstrap Class Loader. Dieser sucht die zu ladende Klasse in der Java API. Findet er die Klasse nicht, so gibt er die Anfrage wieder an seinen Sohn Class Loader weiter, der ebenfalls versucht die Klasse zu laden usw. Kann der Bootstrap Class Loader jedoch die Klasse laden, so gibt er die Klasse an seine Sohnknoten weiter, die dann ihrerseits nicht mehr versuchen die Klasse zu laden. Durch diesen Mechanismus wird verhindert, dass sich eine möglicherweise gefährliche Klasse als trusted class ausgibt. Wäre dies möglich, könnte diese Klasse die SandboxBarriere durchbrechen, da sie fälschlicherweise als sicher angesehen würde. Durch das Prinzip des Hochdelegierens, ist es auch nicht möglich, trusted classes durch eigene Klassen zu überschreiben. Wenn ein Custom Class Loader beispielsweise versuchen würde, eine eigene java.lang.String Klasse zu laden, würde diese Anfrage als erstes bis zum Bootstrap Class Loader nach oben geleitet. Dieser würde feststellen, dass das Paket java.lang zur Java API gehört und die Referenz auf diese Klasse zurückgeben. Auch ein anderes Beispiel führt nicht zu dem gewollten Erfolg. Angenommen, ein Programm möchte die Datei java.lang.Virus laden, welche die Virtual Machine angreifen soll. Analog zum ersten Beispiel würde auch diese Anfrage bis ganz nach oben delegiert werden, der Bootstrap CL würde feststellen, dass er zwar das Paket java.lang kennt, aber die Klasse nicht enthalten ist und würde zurückgeben, dass er die Klasse nicht laden kann. Da auch alle anderen übergeordneten Class Loader die Datei nicht in ihrem Bereich finden können, würde sie also, wie vom Angreifer gewünscht, von dem eigenen Class Loader geladen. Da diese im Paket java.lang liegt könnte man jetzt davon ausgehen, dass diese die gleichen Rechte hat, wie jede Klasse in diesem Paket, beispielsweise auf mit dem Schlüsselwort protected geschützte Methoden und Attribute zuzugreifen. Dies ist aber nicht möglich, da die java.lang API Pakete von einem anderen Class Loader geladen wurden als die java.lang.Virus Klasse. Hier kommt der Begriff des runtime packages ins Spiel, der bedeutet, dass sich zwei (oder mehrere) Klassen nur dann im gleichen runtime package befinden, wenn sie den gleichen Paketnamen haben und sie vom gleichen Class Loader geladen wurden. Da sich, um auf package- sichtbare Variablen und Methoden zugreifen zu können, die beiden Klassen im gleichen runtime package befinden müssen, ist es der hier beispielhaft beschriebenen java.lang.Virus Klasse also nicht möglich, die java.lang Klassen der API zu beeinflussen. [JF] 10 5 Der Class File Verifier Nachdem eine Klasse ausfindig gemacht und vom Class Loader geladen wurde, muss sie noch eine weitere Hürde nehmen, bevor Sie von der Java Virtual Machine (JVM) ausgeführt wird. Zu diesem Zeitpunkt kann man zwar relativ sicher sein, dass sie keine der System Klassen überschrieben hat, dass sie sich nicht den Weg in andere packages bahnt oder andere Klassen, die bereits geladen wurden, beeinflusst. Allerdings kann nicht garantiert werden, dass die Klasse an sich sicher ist. Es gibt zwar den Sicherheitsmanager (siehe Kapitel 6), welcher verhindert, dass die Klasse auf geschützte Ressourcen (Festplatte, Netzwerk) zugreift, aber das ist nicht ausreichend. Die Klasse kann ungültigen Bytecode enthalten, falsche Pointer zu geschützten Speicherbereichen enthalten, Über- oder Unterlauf im Programm Stack verursachen, oder auf einem anderen Weg die Integrität der Java Virtual Machine umgehen. Unter Verwendung eines „normalen“ Java Compiler kann dies zwar ausgeschlossen werden, da die Java Sprache und der Compiler eine hohen Grad an Sicherheit bereitstellen. Allerdings können bösartige Programmierer mit ihrem selbst geschriebenen Compiler Bytecode produzieren, welcher die JVM zum Absturz bringt oder dessen Sicherheitsbeschreibungen untergräbt. Unter Umständen ist es nicht einmal sichergestellt, dass die Ausgangssprache für den Bytecode überhaupt Java war. Ein weiterer wichtiger Punkt ist die binary-Kompatibilität zu Fremdklassen. Nehmen wir einmal an, dass das Applet von mercedes die Klasse Steuergerät von einem Dritthersteller verwendet und sein Applet darauf abstimmt. Was passiert jedoch, wenn das Applet mit einer neueren Version von Steuergerät geladen wird als mit der es entwickelt wurde? Es können mitunter Variablennamen verändert worden sein oder andere Veränderungen stattgefunden haben, welche Auswirkungen auf das Applet haben. Dies kann dazu führen, dass die Binärcode- Kompatibiliät zwischen den einzelnen Versionen nicht mehr gewährleistet ist. Dieses Problem existiert für alle Arten von Bibliotheken, welche in Binärcode weitergegeben werden. Auf den meisten Systemen führt dieses zu Fehlermeldungen oder dass das Programm nicht startet, im schlimmsten Fall kann es jedoch auch zum System-Absturz führen. Aus den soeben genannten Gründen ist eine weitere Überprüfungsstufe notwendig, bevor der Java-Code ausgeführt wird. Dies ist genau der Punkt, an dem der Class File Verifier ins Spiel kommt. Nachdem eine unsichere Klasse von einer Class Loader Instanz geladen wurde, wird diese Klasse an den Class File Verifier übergeben. Dieser stellt sicher, dass die Klasse ohne Probleme ausgeführt werden kann. Der Class File Verifier selber ist Teil der JVM und kann somit nicht umgangen oder überschrieben werden, solange man nicht die JVM an sich austauscht. 11 5.1 Die 4 Phasen des Class File Verifier Der Class File Verifier durchläuft vier Phasen, die einzelnen Phasen werden im folgendem näher beschrieben. Sollte eine dieser Phasen einen Fehler feststellen, wird die Klasse abgelehnt. Jedoch werden nicht alle Phasen vorm Ausführen der Klasse durchlaufen. Die ersten drei werden vorm Ausführen durchlaufen und nur wenn der Code alle Tests besteht wird er zur Ausführung freigegeben. Phase 4, eigentlich eine Reihe von ad hoc Tests, wird zur Ausführungszeit durchgeführt, wenn der Code bereits läuft. Phase 1 – Datei Integritäts- Überprüfung (File Integrity Check) Die erste und einfachste Phase überprüft die Struktur der Klassen-Datei. Sie stellt sicher, dass die Datei die entsprechende Signatur hat (die ersten vier Bytes sind 0xCAFEBABE) und die Strukturen innerhalb der Datei die richtige Länge haben. Es wird überprüft, ob die Datei weder zu kurz noch zu lang ist und der Kostanten Pool nur korrekte Einträge enthält. Natürlich hat jede Datei eine unterschiedliche Länge, aber jede Struktur innerhalb der Datei muss sich von ihrer Länge her innerhalb der Spezifikation bewegen. Wenn in der Datei Struktur Fehler gefunden werden, wird ein Fehler ausgegeben und die Klasse nicht weiter verarbeitet. Phase 2 – Klassen Integritäts- Überprüfung (Class Integrity Check) Die zweite Phase macht alle weiteren Überprüfungen, welche möglich sind ohne die Byte Code Anweisungen an sich zu analysieren. Es wird sichergestellt, dass jede Klasse eine Superklasse hat (solange es sich nicht um ein Objekt handelt). Des Weiteren wird sichergestellt, dass eine Superklasse keine finale Klasse ist und dass Klassen nicht versuchen finale Methoden ihrer Superklasse zu überschreiben. In diesem Schritt wird ebenfalls überprüft, ob Datenfelder und Methodenaufrufe im Konstanten Pool auf gültige Namen und Typen verweisen. In diesem Schritt wird jedoch nicht überprüft, ob die referenzierten Datenfelder, Methoden und Klassen auch wirklich existieren. Phase 3 - Bytecode Integritäts- Überprüfung (Bytecode Integrity Check) In Phase 3 wird der eigentliche Bytecode analysiert. Sie findet während des Linken statt und ist die komplexeste Phase. Für jede Methode einer Klasse wird eine Datenflussanalyse durchgeführt. Dies stellt folgende Punkte sicher, unabhängig wie man zum Aufruf einer bestimmten Anweisung gelangt: - Methoden immer korrekt aufgerufen werden (es werden die Parameter auf Korrektheit überprüfen) - kein Überlauf /Unterlauf auf dem Operanden Stack - Variablen sind initiiert, bevor sie verwendet werden - Sprunganweisungen springen immer an den Anfang einer Instruktion - Anweisungen werden immer mit dem entsprechenden Operandentypen aufgerufen 12 - Felder können nur Werte des korrekten Typs zugewiesen werden Phase 4 – Laufzeit Integritäts- Überprüfung (Runtime Integrity Check) Wie der Name bereits verrät, findet Phase 4 zur Laufzeit statt. Phase 4 findet während des dynamischen Linkings statt, wenn die symbolischen Referenzen einer Klassendatei aufgelöst werden. Dabei werden alle Verweise auf andere Klassen sowie auf Methoden und Attribute anderer Klassen verfolgt und überprüft. Wird eine Anweisung einer Klasse das erste Mal referenziert, so führt die Sun JVM folgende Schritte aus: 1. Die Definition der referenzierten Klasse wird geladen, falls dies nicht schon geschehen ist. 2. Es wird überprüft, ob die ausführende Klasse auf die referenzierte Klasse zugreifen darf 3. Die Klasse wird initialisiert, wenn noch nicht geschehen. Wird eine Methode das erste Mal aufgerufen oder beim Zugriff/Modifikation eines Datenfeldes wird folgendes überprüft: 1. Existiert die referenzierte Methode oder das referenzierte Datenfeld in der gegebenen Klasse? 2. Besitzt die referenzierte Methode oder das referenzierte Datenfeld eine korrekte Beschreibung? 3. Besitzt die auszuführende Methode Zugriff auf die referenzierte Methode oder Datenfeld? 6 Der Sicherheitsmanager Der Sicherheitsmanager ist eine Instanz mit deren Hilfe die Zugriffskontrolle unter Java realisiert ist. Seit der ersten JDK 1.0 ist er als abstrakte Klasse vorhanden (Bild 1.2) und bietet durch die Überprüfung von sicherheitsrelevanten Operationen auf deren Ausführung ein Sicherheitskonzept, was es zum Beispiel in C++ nicht gibt. Gerade durch die sehr verbreiteten Java-Applets, welche meist als Internetanwendung zur Verfügung gestellt werden, ist der Sicherheitsgedanke ein nicht zu missachtender Faktor. Der Sicherheitsmanager wird demnach bei jeder kritischen Operation befragt, ob diese ausgeführt werden soll. Wenn dies nicht der Fall ist, wird eine Exception zurück geliefert, welche die Abweisung der Operation aussagt. Im folgenden sind die Punkte aufgelistet, an denen der Sicherheitsmanager als Kontrollinstanz eingesetzt werden kann: 13 Zugriffe auf das Netzwerk Zugriff auf Systemressourcen Zugriffe auf das Dateisystem Aufrufe von Operationen, die den Zugriff bzw. die Manipulation von Threads ermöglichen Aufruf von Programmen und Betriebssystem-Kommandos Einen Unterschied in der Benutzung des Sicherheitsmanagers findet man bei den normalen Javaprogrammen im Vergleich mit Java-Applets. Bei normalen, lokalen Programmen wird ein Sicherheitsmanager nicht standardmäßig initialisiert. Das heißt, dass ein lokales Javaprogramm nicht an die Restriktionen des Sicherheitsmanagers gebunden ist, außer dieser wird explizit implementiert. Das Beispiel1 verdeutlicht dies: Bsp1: Java Programm mit SecurityManager-Aufruf public class Security { static public void main( String args[] ) { System.out.println("Ohne Sicherheitsmanager-Parameter: System.getSecurityManager() ); } } Führt man das Programm aus, so erhält man als Rückgabewert: „NULL“. Dies zeigt, dass kein Sicherheitsmanager standardmäßig bei lokalen Applikationen aufgerufen wird. 6.1 Der Sicherheitsmanager bei Applets Bei Applets ist es im Gegensatz zu lokalen Java-Applikationen genau anders. Das heißt dass bei Java-Applets der Sicherheitsmanager immer aktiv ist. Dies ist darauf zurückzuführen, dass Applets ein viel höheres Sicherheitsrisiko darstellen, da sie meistens direkt aus dem Internet geöffnet werden und man keine direkte Kontrolle über sie hat. Allein eine falsche Sicherheitseinstellung im Browser hätte zur Folge, dass Code eingeschleust werden kann, Dateien gelöscht werden könnten oder Daten übertragen werden, die nicht übertragen werden sollten. Dies alles wird durch den Sicherheitsmanager unterbunden und überwacht. Folgende Punkte zeigen die Einschränkung eines Applets im Gegensatz zu einer lokalen Java-Anwendung: Dateien dürfen weder gelöscht, erzeugt oder umbenannt werden (delete(), new FileOutputStream(), renameTo()) eine Abfrage ob eine oder mehrere Datei existieren ist nicht möglich (exists()) Verzeichnisse dürfen nicht angelegt werden und auch die Inhalte von Verzeichnissen dürfen nicht gelistet werden (mkdir(), list()) 14 Dateiattribute dürfen nicht eingesehen werden (lastModified(), isFile()) Verbindungen zu anderen als dem Server, von dem das Applet gestartet wurde sind nicht möglich ( new URL( http://ba-horb.de/)) Sockets sind auch nicht erlaubt (new Socket()) Programme, die auf dem lokalen Rechner liegen, dürfen nicht ausgeführt werden (runtime.getRuntime().exec(„dir c:“)) Applets dürfen keinen eigenen Sicherheitsmanager installieren, da sonst Einschränkungen umgangen werden könnten Beendigung einer gemeinsam genutzten virtuellen Maschine ist unzulässig (System.exit(0)) Beispiel2 soll noch einmal verdeutlichen, dass Sicherheitsmanager standardmäßig aktiviert wird. bei einem Java-Applet ein Bsp2: Applet mit SecurityManager-Aufruf public class Sec extends java.applet.Applet { } public void paint( java.awt.Graphics g ) { g.drawString( System.getSecurityManager().toString(), 10, 10 ); } Bei der Ausgabe des Applets steht in der ersten Zeile „sun.applet.AppletSecurity@....“ Dies weißt darauf hin, dass ein Securitymanager aktiviert ist und die oben genannten Aktivitäten wie Dateizugriffe usw. überwacht. Bild 4.1 – Applet mit SecurityManager 15 Applets sind jedoch nicht völlig rechtelos. Es gibt bestimmte Umgebungsvariablen, die sie auslesen können, um so etwas über das System zu erfahren und die Java-Aplikation eventuell anpassen zu können: URL des Herstellers (java.vendor.url) Java-Version (java.version) Hersteller der Java-Umgebung (java.vendor) das Betriebssystem (os.name) die Betriebssystemarchitektur (os.arch) die Betriebssystemversion (os.version) das Symbol, das als Trennzeichen zwischen 2 Verzeichnissen fungiert (path.separator) das Symbol, das als Trennzeichen zwischen Verzeichnis- und Dateinamen besteht (file.separator) das Symbol, welches das Zeilenende repräsentiert (line.separator) 6.2 Aktivierung des Sicherheitsmanagers Bei Applets wird der Sicherheitsmanager standardmäßig aktiviert, aber nicht bei normalen Java-Applikationen, wie schon in den vorigen Kapiteln erläutert wurde. Um auch bei lokalen Java-Applikationen den Sicherheitsmanager zu aktivieren, muss beim Aufrufen des Programms folgende Zeichenfolge an den Javaprogrammaufruf angehangen werden: „-Djava.security.manager“. Führt man ein Programm, das z.B. eine neue Datei anlegen soll, ohne diesen Anhang aus, so läuft es ohne eine Fehlermeldung durch. Benutzt man jedoch den Schalter „-Djava.security.manager“, so kommt eine Fehlermeldung wie in Beispiel 3 gezeigt. Bsp3: Erzeugen eines Files import java.io.*; public class SecTest { public static void main( String args[] ) { System.err.println( System.getSecurityManager() ); File f = new File( "c:/writer.txt" ); if ( f.exists() ) { System.out.println("Datei existiert"); } } } 16 Ausgabe bei gesetztem Sicherheitsmanager: java.lang.SecurityManager@923e30 Exception in thread "main" java.security.AccessControlException: access denied (java.io.FilePermission c:\writer.txt read) at java.security.AccessControlContext.checkPermission(Unknown Source) at java.security.AccessController.checkPermission(Unknown Source) at java.lang.SecurityManager.checkPermission(Unknown Source) at java.lang.SecurityManager.checkRead(Unknown Source) at java.io.File.exists(Unknown Source) at SecTest.main(SecTest.java:10) In der Ausgabe ist zu erkennen, dass der Zugriff verweigert wurde. Ein Erzeugen einer neuen Datei ist nicht möglich. Ist der Sicherheitsmanager nicht aktiviert, erhält man die Nachricht, dass die Datei erzeugt wurde. Ausgabe bei nicht gesetztem Sicherheitsmanager: Datei existiert null 7 Rechteverwaltung Die Rechteverwaltung wird in Java über so genannte Policy-Dateien realisiert. Diese Dateien geben vor, auf welche Dinge der Security-Manager reagieren soll. Mit ihnen ist es also möglich, eigens definierte Rechte zu vergeben. Zum Beispiel kann das Lese- oder Schreibrecht an einer Datei mittels Policy-Datei unterbunden oder explizit erlaubt werden. Alles, was nicht in der Policy-Datei erlaubt ist, ist standardmäßig verboten. Policy-Dateien sind erst ab der Java Version 1.2 vorhanden. Es ist zu beachten dass die in Browsern eingebauten Virtual Machines dieses Sicherheitsmodell nicht unterstützen. Es ist daher zu empfehlen, sich das Java-PlugIn direkt herunter zu laden. Der Internetexplorer 6, der in Windows XP integriert ist, liefert standardmäßig schon gar kein Java-PlugIn bzw. eine Java Virtual Machine mit. Eine Policy-Datei besteht aus Regeln, über welche die Berechtigungen gesteuert werden. Eine Regel besteht aus folgenden Teilen: dem Herkunftsort des Bytecodes – damit ist eine URL oder ein Pfad bei lokalen Anwendungen gemeint die Berechtigung zu einer bestimmten Aktion (z. B. Erstellen einer Datei) einer digitalen Signatur, die der Code tragen muss, damit die Berechtigungen gewährt werden 17 eine Identität (z. B. eine Benutzer-ID), auf die eine Regel beschränkt werden kann Der Herkunftsort und die Berechtigung müssen angegeben werden. Identität und Signatur sind optional. Beim Programmstart werden zwei Policy-Dateien versucht einzulesen. Als erstes ist dies die Standard-Policy-Datei, in der die Grundeinstellungen gespeichert sind. Diese befindet sich standardmäßig im Verzeichnis „<JAVA_HOME>/jre/lib/security“. Die Rechtevergabe beschränkt jedoch fast alles. Als zweite Datei wird die benutzerdefinierte Policy-Datei versucht einzulesen. Diese Datei muss die Endung „.java.policy“ tragen und im HOME-Verzeichnis des Benutzers liegen. Ist die benutzerdefinierte Policy-Datei nicht vorhanden, wird natürlich nur die StandardPolicy-Datei für den Securitymanager zugrunde gelegt. Sobald aber beide PolicyDateien vorhanden sind, werden deren Einträge addiert und es stehen die Berechtigungen der in den beiden Dateien gespeicherten Regeln zur Verfügung. Wenn keine dieser Dateien vorhanden ist, kommt eine Default-Policy zur Anwendung, die das Auslesen der System-Properties sowie das Binden von Sockets an nicht privilegierte Ports (d. h. höher als 1023) gestattet. Für die Erstellung der Rechte gibt es die Möglichkeit dies mit einem grafischen Tool zu erledigen. Dieses Tool sieht man in Bild 5.1. Mit ihm können ganz einfach Rechtedateien geladen und editiert werden oder aber neue Dateien erstellt werden. Bild 5.1 - Richtlinientool In Bild 5.2 kann man neue Berechtigungen hinzufügen. Beim Anlegen eines neuen Rechts müssen drei Parameter eingestellt werden: 18 Als erstes muss die Berechtigung an sich eingestellt werden. Für jede Berechtigung ist eine eigene Klasse vorzufinden, die von der Klasse „java.security.Permission“ abgeleitet ist. Für jede Berechtigung gibt es Targets. Ein Target gibt detailliert an, worauf ein Zugriff erfolgen darf. Im Falle der Klasse java.io.FilePermission würde das Target die Datei angeben, auf die der Zugriff erlaubt wird. Als Drittes ist die Aktion anzugeben, welche die Art des Zugriffs beschreibt. Bei einem Dateizugriff kommen z. B. die Zugriffsrechte „schreiben“, „lesen“ oder „lesen und schreiben“ in Frage. Neben einzelnen Berechtigungen können auch alle Rechte mit einmal vergeben werden. Dieses wird durch den Eintrag „java.security.AllPermission“ vollführt. Bild 5.2 – Richtlinientool/Berechtigung hinzufügen 8 Signing Mit der Signierung von z.B. Applets lassen sich Rechte dynamischer anpassen. Wenn zum Beispiel ein User ein Applet als vertrauenswürdig einstuft, können diesem mehr Rechte zugewiesen werden als wenn der User es nicht als vertrauenswürdig einstuft. Ab dem JDK 1.2 können die Rechte frei vergeben werden. In den Versionen davor bekam das Applet alle Rechte wie es auch ein lokales Programm hatte. 19 Beim Signing wird das so genannte Public-Key-Verfahren angewandt. Dazu wird ein Schlüsselpaar erzeugt, welches aus einem privaten Schlüssel (private Key) und einem öffentlichen Schlüssel (public key) besteht. Dieses Verfahren wird heutzutage auch bei vielen anderen Sicherheitsverfahren angewandt und ist weltweit bekannt. Bei diesem Verfahren ist darauf zu achten, dass der private Schlüssel nur dem Schlüsselerzeuger bekannt sein darf bzw. sollte. Der öffentliche Schlüssel hingegen ist, wie der Name schon sagt, jedem zugänglich zu machen. Um eine Nachricht oder ein Programm weiter zu verschlüsseln, ist der private Schlüssel notwendig. Mit dem öffentlichen Schlüssel kann dann decodiert werden. Das Ganze wird nun auf Applets angewandt. Dazu wird ein Hashwert gebildet, welcher dann mittels des privaten Schlüssels codiert wird. Dieser so verschlüsselte Hashwert wird nun an die Ursprungsnachricht bzw. Datei angehangen. Ein Nutzer, der nun ein solches Applet öffnen möchte, muss mit Hilfe des öffentlichen Schlüssels, den der Ersteller des Applets mitliefert, den Hashwert decodieren und mit dem selbst errechneten Hashwert der Nachricht bzw. der Datei vergleichen. Sind beide identisch, kann davon ausgegangen werden, dass das Programm bzw. die Nachricht nicht manipuliert wurden. Dies geschieht jedoch im Hintergrund und für den Nutzer unsichtbar. Einzig und alleine eine Bestätigung über die Zulassung der Vertrauenswürdigkeit wird von den Browsern abgefragt. Hashwertbildung Öffentlicher Schlüssel Java Klassen und Datendateien JavaProgramm + angehängten Hashwert Java Klassen und Datendateien Hashwert entschlüss eln Eigenen Hashwert bilden Vergleich der Hashwerte Programm Vertrauenswürdig Bild 6.1 – Modell der Signierung 8.1 Erstellen eines Signierten Applets Um signierte Applets zu erstellen sind 3 Schritte notwendig. Als erstes muss ein Schlüsselpaar erzeugt werden, was mit dem Tool „keytool“ ermöglicht wird. Dazu wird in der Kommandozeile „keytool -genkey -alias thomas“ eingegeben. Der erste Parameter „-genkey“ ist wichtig, da dadurch die Generierung eines neuen Schlüsselpaares einhergeht. Der zweite Parameter „-alias thomas“ gibt den Schlüsselinhaber an. Danach 20 fragt das Pogramm noch nach einem Passwort und weiteren Daten die den Schlüsselinhaber betreffen. Dazu folgend ein Beispiel: Geben Sie das Keystore-Passwort ein: [fish]: ba-horb Wie lautet Ihr Vor- und Nachname? [fish]: Thomas Fischer Wie lautet der Name Ihrer organisatorischen Einheit? [fish]: Informationstechnik Als nächstes ist ein JAR-Archiv zu erstellen, welches im nächsten Schritt für die Signierung gebraucht wird. Das erstellen eines JAR-Archivs erfolgt mit folgender Anweisung: jar cvf ba-horb.jar *.class Im dritten und letzten Schritt wird das gerade erstellte JAR-Archiv signiert. Dazu wird auch das gerade erstellte Schlüsselpaar benötigt, bzw. dessen privater Schlüssel. Die Signierung ist wie folgt mit dem Tool „jarsigner“ aufzurufen: jarsigner ba-horb.jar thomas Enter Passphrase for keystore: ba-horb Das nun erzeugte File besteht aus dem eigentlichen JAR-Archiv und einem angehängten verschlüsselten Hashwert, der mit Hilfe des privaten Schlüssels codiert wurde. Eine Decodierung ist nur mit dem zugehörigen öffentlichen Schlüssel möglich und stellt folglich eine gute Sicherung gegen Missbrauch dar. 9 Zusammenfassung Java kann man ohne weiteres als eine recht sichere Sprache ansehen. Sicherheitsaspekte wurden im Design berücksichtigt und ein Sicherheitskonzept ist klar erkennbar. Durch die stetige Weiterentwicklung von Java wurden im Laufe der Zeit noch einige Erweiterungen beigefügt, wie zum Beispiel die „Policy-Dateien“, die es erst ab Version 1.2 gibt und die einen weiteren Sicherheitsmechanismus bereitstellen. Java ist damit ohne weiteres für sicherheitsrelevante Anwendungen einsetzbar, genauso wie für sicherheitsrelevante Internetanwendungen. 10 Literaturverzeichnis [JF1] Fitzinger, Johanna: Seminararbeit “Java Security”. [MP97] Pilz, Markus: Das Sicherheitskonzept von Java, 1997 21 [GO03] Ullenboom, Christian: Galileo Computing – Java ist auch eine Insel, 2003 [JP98] Posegga, Joachim: Springer Verlag – Die Sicherheitsaspekte von Java, 1998 [WI05] http://www.wikipedia.de [SU05] http://java.sun.com [IT05] http://ww.indictthreads.com [OR05] http://www.unix.org.ua/orelly/java-ent/security/ch03_01.htm [ML05] http://medialab.di.unipi.it/doc/JNetSec/jns_ch5.htm 22