Semesterarbeit Software Engeneering

Werbung
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
Herunterladen