Zum Inhalt springen

Groovy: Sicherheitsfragen

Aus Wikibooks

Die dynamische Integration von Skripten in Ihre Java-Programme wirft spezielle Fragen nach der Sicherheit auf. Solchen Skripten sollte nicht erlaubt werden, Aktionen zu unternehmen, die die Funktionalität der Anwendung stören. Die Ursache dafür muss ja nicht unbedingt Böswilligkeit sein -- auch wenn der Benutzer sich irrt oder sich über die Konsequenzen seines Handelns nicht bewusst ist, können die Folgen fatal sein.

Zugriffsrechte beschränken

[Bearbeiten]

Rufen Sie doch noch einmal die MiniKonsole von oben auf und geben Sie ein:

> java -cp %GROOVY_HOME%\embeddable\groovy-all-1.1.jar;. MiniKonsole
groovy> System.exit(1)

Wie Sie sehen, verabschiedet sich das Programm sang- und klanglos. Das ist in diesem Fall natürlich nicht so schlimm, aber wenn das Skript beispielsweise im Rahmen einer unternehmenskritischen Server-Anwendung mit Hunderten von Benutzern läuft, sollte so etwas besser vermieden werden.

Java bietet eine Möglichkeit, die Rechte von Teilen eines Programms bezüglich bestimmter Aktionen zu begrenzen. Solche Aktionen sind Zugriffe auf externe Ressourcen, zum Beispiel Dateien, Netzwerkverbindungen oder Datenbanken, oder kritische Manipulationen der virtuellen Maschine, wie etwas das eben ausprobierte System.exit(). Sie können auch Methoden Ihrer eigenen Klassen schützen.

Als Voraussetzung dafür, dass diese Schutzmechanismen überhaupt wirksam werden können, müssen Sie Ihre Anwendung unter Kontrolle eines Security-Managers unterstellen; dies ist eine Klasse mit bestimmten Eigenschaften; im Allgemeinen genügt es, durch Setzen des Schalters -Djava.security.manager den seit Java 1.2 standardmäßig mit dem JDK gelieferten Security-Manager zu aktivieren. Dieser Standard-Security-Manager erlaubt erst einmal allen zum JDK gehörenden Klassen alles. Darüber hinaus sind alle kritischen Aktionen allen Klassen verwehrt, sofern sie nicht in einer sogenannten Policy-Datei eigens eine Erlaubnis erhalten haben. Dabei werden die Klassen, die eine solche Erlaubnis bekommen sollen, durch ihre Codebase gekennzeichnet, das ist der Dateiname bzw. die URL des Verzeichnisses oder der JAR-Datei, in dem sich die entsprechenden .class-Dateien befinden.

Das Ziel besteht nun darin, allen Java-Programmen alle Rechte zu geben (wie es ohne Security-Manager auch der Fall ist) und nur die dynamisch ausgeführten Groovy-Skripte so zu beschränken, dass diese keinen Schaden anrichten können. So einen abgeschotteten Programmteil bezeichnet man auch als Sandbox (Sandkasten). Gehen Sie dazu folgendermaßen vor.

Schreiben Sie zunächst eine Policy-Datei, in der Sie Ihrem Programm sowie allen von diesem Programm benötigten Bibliotheken - darunter befindet sich auch die JAR-Datei von Groovy - alle Rechte verleihen. Der Name der Datei ist egal; wir nennen Sie einer Konvention folgend MiniKonsole.policy (vgl. folgendes Beispiel).

// Policy-Datei MiniKonsole.policy
grant codeBase "file:./bin" {
  permission java.security.AllPermission;
};
grant codeBase "file:./lib/*" {
  permission java.security.AllPermission;
};

In dieser Policy-Datei gehen wir davon aus, dass sich die Klassendateien Ihres Programms in einem Unterverzeichnis namens bin und alle benötigten JAR-Dateien in einem Unterverzeichnis namens lib befinden. Diese Pfade müssen Sie natürlich Ihrer Anwendung entsprechend anpassen.

Rufen Sie nun die MiniKonsole so auf, dass erstens der Security-Manager aktiviert und zweitens die Policy-Datei benannt wird. Dies geschieht mit den Java-Schaltern -Djava.security.manager -Djava.security.policy=policy-datei, die Sie beim Aufruf des Programms mit angeben.

> java -cp %GROOVY_HOME%\embeddable\groovy-all-1.5.jar;. MiniKonsole
          -Djava.security.manager
          -Djava.security.policy=MiniKonsole.policy
          MiniKonsole
groovy> System.exit(1)
java.security.AccessControlException: access denied (java.lang.RuntimePermission exitVM.1)

Wenn Sie jetzt versuchen, die virtuelle Maschine gewaltsam abzubrechen, bekommen Sie durch eine AccessControlException den deutlichen Hinweis, dass Sie versucht haben, etwas Verbotenes zu tun. Aber das Programm wird dadurch nicht mehr beendet, denn unsere MiniKonsole fängt diese Exception ab wie alle anderen auch.

Die Möglichkeiten von dynamischen Groovy-Skripten innerhalb Ihrer Programme sind auf diese Weise ziemlich begrenzt. In vielen Fällen, zum Beispiel wenn die Skripte zum Berechnen von Formeln dienen oder für einfache Steuerungsaufgaben vorgesehen sind, kann dies durchaus reichen. Wenn Sie Ihre Skripte mit weiter gehenden Befugnissen ausstatten möchten, haben Sie zwei Möglichkeiten: entweder über eine Erweiterung der Policy-Datei oder mit Hilfe privilegierter Aktionen.

Erweiterte Rechte für Groovy-Skripte

[Bearbeiten]

Wenn Sie den dynamischen Skripten zusätzliche Rechte einräumen möchten, können Sie diese im Prinzip - wie bei allen anderen Programmteilen auch - in die Policy-Datei eintragen. Das Problem ist nur, dass es hier keine Codebase gibt, der Sie die Rechte zuordnen können, denn das Programm kann aus irgendwelchen Quellen kommen, und es gibt überhaupt keine Klassendateien. Um dieses Problem lösen zu können, bietet die Groovy-Bibliothek eine virtuelle Codebase an, der Sie irgendeinen Namen geben können - der nicht unbedingt auf eine existierende Datei oder sonstige Datenquelle verweisen muss -, und dieser Codebase können Sie Rechte geben wie jeder anderen.

Wir müssen zu diesem Zweck die MiniKonsole derart umprogrammieren, dass wir interaktiv aus den eingegebenen Skriptzeilen ein GroovyCodeSource-Objekt erzeugen und dieses anstelle des eigentlichen Skript-Codes an die GroovyShell übergeben. Der entsprechende Ausschnitt sieht so aus:

// Java
GroovyCodeSource gcs = new GroovyCodeSource(zeile,"tempScript","/scripts");
Script script = shell.parse(gcs);

Die GroovyCodeSource bekommt in diesem Konstruktor drei Strings übergeben: den Text des Skripts, einen Namen für das Skript und einen Namen für die virtuelle Codebase, der nicht auf ein wirklich existierendes Verzeichnis verweisen muss. Diesen Namen können Sie nun dazu verwenden, die Policy-Datei um spezielle Berechtigungen für das Skript zu erweitern:

grant codeBase "file:/scripts" {
  permission java.io.FilePermission "-", "read"; 
  permission java.util.PropertyPermission "file.encoding", "read";
};

Wir haben die Codebase file:/scripts hier um die Berechtigung erweitert, beliebige Dateien im aktuellen und allen unterliegenden Verzeichnissen zu lesen. Zusätzlich benötigen wir noch die Berechtigung, die System-Property file.encoding auszulesen, denn diese wird benötigt, um den Zeichensatz für die Dekodierung des Textes zu bestimmen. Wir starten die veränderte MiniKonsole mit der veränderten Policy-Datei und versuchen Folgendes (unter der Annahme, dass eine Datei beispiel.txt im aktuellen Verzeichnis existiert):

groovy> println new File("beispiel.txt").text

Das sind die Daten

groovy> new File("beispiel.txt").write("Das sind die Daten")
java.security.AccessControlException: access denied (java.io.FilePermission beispiel.txt write)

Tatsächlich können wir auf eine Datei im aktuellen Verzeichnis lesend zugreifen, während der Versuch, in dieselbe Datei zu schreiben, an der Fehlenden Erlaubnis scheitert.

Privilegierte Aktionen

[Bearbeiten]

Wenn Sie die Rechte eines Skripts feiner steuern möchten, bietet sich die Verwendung privilegierter Methoden an, die Sie den Skripten zur Verfügung stellen. Im Abschnitt Dynamische Integration haben wir gezeigt, wie sie mit Hilfe einer eigenen Basisklasse dem Skript zusätzliche Methoden zur Verfügung stellen können. Wir erweitern diese Klasse um die Methode getUserName(), die den aktuellen Benutzernamen aus den System-Properties liest.

// Java
public String getUserName() {
  return AccessController.doPrivileged(new PrivilegedAction<String>() {
    public String run() {
      return System.getProperty("user.name");
    }
  });
}

Die Methode ruft die System-Property nicht direkt auf, sondern tut dies über eine PrivilegedAction; dies hat zur Folge, dass die Methode auch dann ausgeführt wird, wenn sie aus unsicherem Code heraus aufgerufen worden ist. Mit dieser Erweiterung ausgeführt, können Sie nun legal den Benutzernamen abfragen, während das Auslesen des Home-Verzeichnisses nach wie vor nicht möglich ist:

groovy> println userName
testuser
groovy> println System.getProperty("user.home")
java.security.AccessControlException: access denied (java.util.PropertyPermission user.home read)

Ein Vorteil von privilegierten Methoden besteht darin, dass Sie damit eine genau definierte Aktivität zulassen. Das ist mit einer Security-Policy nicht immer in der gewünschten Granularität möglich.

Dieser Hinweis auf die Möglichkeiten, dynamische aus Java-Programmen heraus aufgerufene Groovy-Skripte abzusichern, sollte an dieser Stelle genügen. In jedem Fall ist es zu empfehlen, dass Sie sich intensiver mit der Java-Sicherheitsarchitektur auseinandersetzen, wenn Sie in kritischen Anwendungen mit dynamischen Skripten arbeiten wollen.

Warnung: Denken Sie auch daran, dass Sie auch dann, wenn Sie benutzerdefinierte Skripte in der völlig abgeschotteten Sandbox laufen lassen, eine Sicherheitslücke öffnen. Beispielsweise wird es etwa einem Kundigen nicht schwer fallen, sich ein Skript auszudenken, das Ihren Rechner einfach lahmlegt, ohne dabei auf irgend eine geschützte Information zugreifen zu müssen. Wenn Sie also nicht genau wissen, wer die möglichen Autoren der Skripte sind und was diese im Schilde führen könnten, sollten Sie auf jeden Fall weitere Maßnahmen ergreifen, um die Verfügbarkeit Ihres Systems abzusichern.

Am Ende dieses Kapitels über die Integration von Groovy- und Java-Programmen sollte noch angemerkt werden, dass Groovy in dieser Hinsicht Möglichkeiten zur Verfügung stellt, die kaum bei einer anderen Java-basierten Sprache mit Skript-artigen Features zu finden sein wird. Das macht Groovy zu der Sprache für dynamische Erweiterungen von in Java geschriebenen Programmen.

Mit diesem Kapitel beenden wir auch unsere Betrachtung der Möglichkeiten, die Ihnen Groovy zum Programmieren zur Verfügung stellt und wenden uns der Frage zu, wie Sie Groovy als Tool einsetzen können, um den Software-Produktionsprozess - auch für Anwendungen, die nicht in Groovy selbst programmiert werden - effektiver zu gestalten.