Sicherheitsberater in Java

Es soll ein eigener Sicherheitsmanager implementieren werden, und dazu eine Unterklasse von SecurityManager gebildet werden.

Der Sicherheitsmanager besitzt für die Überprüfungen eine Reihe von checkXXX()-Methoden, die von den zugehörigen potenziell gefährlichen Methoden aufgerufen werden. Es liegt nun an uns, welche der checkXXX()-Methoden wir überschreiben, um aktiv in die Sicherheit einzugreifen.

Eine Methode haben wir schon kennengelernt: checkRead(). Darf ein Programm vom Dateisystem nicht lesen, löst die entsprechende Überprüfungsfunktion eine SecurityException aus. Soll ein Programm etwa als Applikation oder alternativ als Applet arbeiten können, lässt sich über eine aufgefangene SecurityException leicht herausfiltern, was momentan möglich ist, und was nicht.

Die folgende Tabelle führt die Methoden in der Klasse SecurityManager auf, zusammen mit der Gruppe, in der die Methoden einzuordnen sind.

Sockets

  • checkAccept(String host, int port)
  • checkConnect(String host, int port)
  • checkConnect(String host, int port,
  • Object executionContext)
  • checkListen(int port)

Threads

  • checkAccess(Thread thread)
  • checkAccess(ThreadGroup threadgroup)

Klassenlader

  • checkCreateClassLoader()

Dateisystem

  • checkDelete(String filename)
  • checkLink(String library)
  • checkRead(FileDescriptor filedescriptor)
  • checkRead(String filename)
  • checkRead(String filename, Object executionContext)
  • checkWrite(FileDescriptor filedescriptor)
  • checkWrite(String filename)

Systemkommandos

  • checkExec(String command)

Interpreter

  • checkExit(int status)

Pakete

  • checkPackageAccess(String packageName)
  • checkPackageDefinition(String packageName)

Eigenschaften

  • checkPropertiesAccess()
  • checkPropertyAccess(String key)
  • checkPropertyAccess(String key, String def)

Netzwerk

  • checkSetFactory()

Fenstersystem

  • checkTopLevelWindow(Object window)

Die Überprüfungsfunktionen können von uns natürlich beliebig programmiert werden. Denkbar ist zum Beispiel, dass jede Operation erlaubt oder jede Operation verhindert wird. Dies ist einfach und trifft für Applikationen und auf einige Methoden von Applets zu. Doch die Methoden könnten noch andere Überprüfungen realisieren. Sie könnten so programmiert werden, dass

  • sie nur einmal ausgeführt werden können
  • der Benutzer um Erlaubnis gefragt wird
  • jede Methode in einer Protokolldatei die Operationen sichert
  • eine Zugriffsliste für Objekte verwaltet wird, also eine eine Liste mit privilegierten Objekten, denen die Operation erlaubt wird.

Für jede checkXXX()-Methode lässt sich dieses Verhalten individuell festlegen.
Die abstrakte Klasse SecurityManager ist so programmiert, dass alle checkXXX()-Methoden erst einmal eine SecurityException werfen:

public void checkXXX(. . .) {
  throw new SecurityException();
}

Das bedeutet für uns natürlich Arbeit, da wir erst einmal viele checkXXX()-Methoden überschreiben dürfen, da normalerweise nicht alles verboten ist. Gehen wir erst einmal einige Beispiele durch, in denen ein eigener Sicherheitsmanager Sinn macht. Wenn wir etwa einen Web-Server programmieren, dann wollen wir nicht, dass er durch Programmierfehler die Systemsicherheit aufs Spiel setzt. Wir können also die checkRead()-Methode, die bei jedem Leseaufruf aufgerufen wird, so implementieren, dass der Server nur Dateien freigibt, bei denen der Dateiname mit dem Pfad zum Basisverzeichnis beginnt. Wenn der Dateiname vom Basisverzeichnis abweicht und auf irgendwelche anderen Dateien auf dem Rechner verweist, dann können wir davon ausgehen, dass ein Programmierfehler vorliegt.

Ein anderes Beispiel: Wir wollen eine geschützte Shell in Java programmieren, sodass der Benutzer nur bestimmte externe Programme (wie "Erstelle Verzeichnis über mkdir") als Kommandos ausführen kann. Alle Kommandos werden mittels exec() ausgeführt. Die exec()-Implementierung testet mit checkExec(), ob der Aufruf gestattet ist. Hier können wir ansetzen und einen String etwa daraufhin untersuchen, ob er mit problematischen Zeichenfolgen beginnt, etwa mit "rm" oder "del".

Eine Benutzerabfrage

Wir haben schon ein anderes Beispiel angesprochen, nämlich die Benutzerabfrage. Wir konstruieren einen PasswordSecurityManager, der nur dann Zugriff erlaubt, wenn vor einer Operation ein Passwort eingeben wurde. Das Passwort wird während der Objekterzeugung übergeben und bei einer Dateioperation überprüft. Wir wollen den Benutzer nicht damit belasten, bei jedem Zugriff das Passwort noch einmal eingeben zu müssen. Wir behandeln Lese- und Schreibzugriffe gleichwertig.

import java.io.*;

class PasswordSecurityManager extends SecurityManager
{
  PasswordSecurityManager( String s )
  {
    password = s;
  }

  private boolean accessOK()
  {
    if ( password != null )
    {
      BufferedReader in = new BufferedReader(
            new InputStreamReader(System.in) );

      System.out.print( "Bitte Passwort eingeben: " );
      try {
        String s = in.readLine();
				
        if ( s!= null && password.equals(s.trim()) )
        {
          password = null;
          return true;
        }
        else
          return false;
      }
			catch (IOException e) { return false; }
    }
    else
      return true;
  }

  public void checkRead( FileDescriptor filedescriptor )
  {
    if ( !accessOK() )
      throw new SecurityException("lesen");
  }
  public void checkRead( String filename )
  {
    if ( !accessOK() )
      throw new SecurityException("lesen");
  }
  public void checkRead(String filename, Object executionContext)
  {
    if ( !accessOK() )
      throw new SecurityException("lesen");
  }
  public void checkWrite( FileDescriptor filedescriptor )
  {
    if ( !accessOK() )
      throw new SecurityException("schreiben");
  }
  public void checkWrite( String filename )
  {
    if ( !accessOK() )
      throw new SecurityException("schreiben");
  }

  private String password;
}


public class UseOwnSecurityManager
{
  static public void main( String args[] ) throws IOException
  {
    System.setSecurityManager(new PasswordSecurityManager("u"));

    try
    {
      FileInputStream in = new FileInputStream("SimplCon.java");

      System.out.print( (char) in.read() );
      System.out.print( (char) in.read() );

    } catch ( SecurityException e ) {
      System.out.println( e + " darfst du nicht!" );
    }
  }
}

Übersicht über die Methoden

Allen checkXXX()-Methoden ist gemeinsam, dass sie eine SecurityException werfen, wenn die Operation nicht erlaubt ist.

class java.lang.SecurityManager

  • SecurityManager()
    Erzeugt einen neuen SecurityManager. Hat eine Applikation schon einen Sicherheitsmanager installiert und RuntimePermission("createSecurityManager") erlaubt das Erzeugen nicht, folgt eine SecurityException. Wir können das Vorhandensein leicht testen, indem wir mittels System.getSecurityManager() eine Referenz holen. Ist diese null, so gibt es noch keinen Manager.
  • Object getSecurityContext()
    Liefert ein Objekt, genauer gesagt, einen AccessControlContext, das die aktuelle Umgebung wiederspiegelt. Dieses kann später von anderen Methoden verwendet werden. Das Objekt wird etwa von der dreiargumentigen checkConnect()-Methode benötigt oder von der zweiargumentigen checkRead()-Methode.
  • void checkPermission( Permission perm )
    Haben wir die Berechtigung perm? Ruft direkt AccessController.checkPermission(perm) auf.
  • void checkPermission( Permission perm, Object context )
    Haben wir die Berechtigung perm? context muss ein Objekt vom Typ AccessControlContext sein, das wir mit getSecurityContext() holen können. Ruft AccessControlContext.checkPermission(perm) auf.
  • void checkCreateClassLoader()
    Darf der Thread einen neuen Klassenlader erzeugen? Die Methode ruft checkPermission() mit RuntimePermission("createClassLoader") auf. Eingesetzt in ClassLoader. ClassLoader().
  • void checkAccess( Thread t )
    Wird von stop(), suspend(), resume(), setPriority(), setName() und setDaemon() der Thread-Klasse genutzt, bevor auf den Thread zugegriffen wird. Darf der Thread t diese Operationen nicht durchführen, etwa wenn t der Thread ist, auf den die Operation angewendet werden soll bekommen wir eine SecurityException. Es ruft etwa t.setName("-") dann sec.checkAccess(t) auf. Ruft checkPermission() mit der Permission RuntimePermission("modifyThread") auf.
  • void checkAccess( ThreadGroup g )
    Darf der aktuell laufende Thread die Thread-Gruppe verändern? Wird aufgerufen, wenn ein neuer Kind-Prozess oder eine Kind-Thread-Gruppe erzeugt wird. Ebenso von setDaemon(), setMaxPriority(), stop(), suspend(), resume() und destroy() der Klasse ThreadGroup. Ruft checkPermission() mit RuntimePermission("modifyThreadGroup") auf.
  • void checkExit( int status )
    Können wir die JVM mit Runtime.exit() beenden? Ruft checkPermission() mit RuntimePermission("exitVM") auf. Der Parameter status steht für den Rückgabewert an die Umgebung, wird aber für sich Sicherheitsprüfung nicht mit einbezogen.
  • void checkExec( String cmd )
    Kann unser Thread externe Programme starten? Wird von der Runtime-Klasse in den drei exec()-Methoden angewendet. Ruft checkPermission() mit File Permission(cmd,"execute") auf.
  • void checkLink( String lib )
    Darf eine Betriebssystem-Bibliothek mit native Code nachgeladen werden? Wird in der Klasse Runtime von load() und loadLibrary() genutzt. Ruft checkPermission() mit RuntimePermission("loadLibrary."+lib) auf.
  • void checkRead( FileDescriptor fd )
    Können wir eine Datei lesen? Ruft checkPermission() mit RuntimePermission("read FileDescriptor") auf.
  • void checkRead( String file )
    Können wir die Datei file lesen? Ruft checkPermission() mit FilePermis sion(file,"read") auf.
  • void checkRead( String file, Object context )
    Erlaubt das Sicherheitsobjekt context eine Operation auf der Datei file? Der context wird durch getSecurityContext() ermittelt. Ist dieses Objekt vom Typ AccessControlContext, dann wird AccessControlContext.checkPermission() mit dem Parameter FilePermission(file,"read") aufgerufen. Wenn context kein AccessControlContext ist, bekommen wir eine SecurityException, also wird uns der Lesezugriff verwehrt.
  • void checkWrite( FileDescriptor fd )
    Können wir in die Datei schreiben? Ruft checkPermission() mit RuntimePermission("writeFileDescriptor") auf.
  • void checkWrite( String file )
    Können wir file schreiben? Ruft checkPermission() mit FilePermission(file,"write") auf.
  • void checkDelete( String file )
    Können wir die Datei löschen? Genutzt von File.delete(). Ruft checkPermission() mit FilePermission(file,"delete") auf.
  • void checkConnect( String host, int port )
    Dürfen wir eine Verbindung zum Host am Port aufbauen? Ruft checkPermission() mit SocketPermission(host+":"+port,"connect") auf. Ist port gleich -1, dann möchte die Methode die IP-Adresse des Hosts erfahren. Dann wird checkPermission() mit SocketPermission(host,"resolve") aufgerufen.
  • void checkConnect(String host, int port, Object context)
    Dürfen wir eine Verbindung zum Host am Port aufbauen? Ruft checkPermission() mit SocketPermission(host+":"+port,"connect") auf. Ist port gleich -1, dann möchte die Methode die IP-Adresse des Hosts erfahren. Dann wird checkPermission() mit SocketPermission(host,"resolve") aufgerufen. Ist context ein Exemplar vom Typ AccessControlContext, dann wird AccessControlContext.checkPermission() mit dem Parameter SocketPermission(host+":"+port,"connect") aufgerufen. Ist port = -1 wird die entsprechende Funktion checkPermission() mit SocketPermission(host,"resolve") aufgerufen. Ist context kein AccessControlContext-Objekt wird eine SecurityException ausgelöst.
  • void checkListen( int port )
    Dürfen wir an der angegebenen Portnummer auf eine Verbindung horchen? Ist der Port ungleich Null, so wird checkPermission() mit SocketPermission("localhost: "+port,"listen") aufgerufen. Ist der Port Null, so wird checkPermission() mit SocketPermission("localhost:1024-","listen") verwendet.
  • void checkAccept( String host, int port )
    Kann der Thread vom Host am Port eine Verbindung annehmen? Ruft checkPermission() mit SocketPermission(host+":"+port,"accept") auf. Wird von ServerSocket. accept() benutzt.
  • void checkMulticast( InetAddress maddr )
    Dürfen wir join/leave/send/receive IP-Multicast nutzen? Ruft checkPermission() mit SocketPermission(maddr.getHostAddress(),"accept,connect") auf.
  • void checkMulticast( InetAddress maddr, byte ttl )
    Dürfen wir join/leave/send/receive IP-Multicast mit dem Byte ttl nutzen? Ruft checkPermission() mit SocketPermission(maddr.getHostAddress(),"accept,connect") auf.
  • public void checkPropertiesAccess()
    Können wir die Systemeigenschaften ändern? Wird von getProperties() und setProperties(java.util.Properties) der System-Klasse benutzt und wird von checkPermission() mit PropertyPermission("*", "read,write") gesteuert.
  • void checkPropertyAccess( String key )
    Haben wir Zugriff auf die Systemeigenschaft key? Genutzt von System.getProperty(String). Testet es durch checkPermission() mit PropertyPermission(key, "read").
  • boolean checkTopLevelWindow( Object window )
    Können wir ein Fenster öffnen? Wenn nicht, dann bekommen wir die SecurityException.Die Methode hat im Gegensatz zu den anderen Methoden den Rückgabewert boolean. So lässt sich testen, ob eine Ausgabe ohne zusätzliche Meldung möglich ist. Ist der Wert true, so wird keine Meldung ausgeben. Ruft checkPermission() mit AWT Permission("showWindowWithoutWarningBanner") auf.
  • void checkPrintJobAccess()
    Können wir Druckaufträge schicken? Ruft checkPermission() mit RuntimePermission("queuePrintJob") auf.
  • void checkSystemClipboardAccess()
    Haben wir Zugriff auf die Zwischenablage? Ruft checkPermission() mit AWTPermission("accessClipboard") auf.
  • void checkAwtEventQueueAccess()
    Haben wir Zugriff auf die AWT-Schlange für Benutzer-Eingabeereignisse? Ruft checkPermission() mit AWTPermission("accessEventQueue") auf.
  • public void checkPackageAccess( String pkg )
    Haben wir Zugriff auf die Klassen im Paket mit dem Namen pkg? Wird vom Klassenlader in ClassLoader.loadClass(String, boolean) genutzt. Ruft checkPermission() mit RuntimePermission("accessClassInPakkage."+pkg) auf.
  • void checkPackageDefinition( String pkg )
    Können wir zusätzliche Klassen im Paket pkg definieren? Wird von einigen Klassenladern in ClassLoader.loadClass(String, boolean) benutzt. Ruft checkPermission() mit RuntimePermission("defineClassInPackage."+pkg) auf.
  • void checkSetFactory()
    Wenn keine neue Socket-Fabrik durch die Socket (Socket.setSocketImplFactory(Socket ImplFactory)) bzw. ServerSocket (ServerSocket.setSocketFactory(java.net.SocketImpl Factory)) oder URL-Klasse (URL.setURLStreamHandlerFactory(URLStreamHandlerFactory)) installiert werden kann, folgt eine SecurityException. Ruft checkPermission() mit RuntimePermission("setFactory") auf.
  • void checkMemberAccess( Class clazz, int which )
    Testet, ob ein Programm Zugriff auf die Method- bzw. Field-Objekte für die Methoden und Eigenschaften der Klasse clazz hat. Wenn nicht, folgt eine SecurityException. Standardmäßig eingestellt ist der Zugriff auf öffentliche Methoden und Eigenschaften. Es kann nur auf Klassen zugegriffen werden, die von demselben Klassenlader wie die zugreifende Klasse geladen wurden. Ruft RuntimePermission("accessDeclaredMembers") auf. which kann die Werte PUBLIC oder DECLARED annehmen und beschreibt auf welche Methoden und Eigenschaften Zugriff gewünscht wird.
  • void checkSecurityAccess(String action)
    Ruft checkPermission() mit SecurityPermission(action) auf, um diese Operation zu testen.
  • ThreadGroup getThreadGroup()
    Liefert die Thread-Gruppe, in der neue Threads erzeugt werden. In der Regel ist dies die Thread-Gruppe des aktuellen Threads. Kann vom Sicherheitsmanager überschrieben werden, um Threads in einer anderen Gruppe anzulegen.