SecurityManager: Reflection

Hallo :slight_smile:

Ich bräuchte etwas Hilfe mit dem SecurityManager. Ich habe ein Programm, das Code von anderen ausführt (also sozusagen Plugins). Ich würde gerne den Zugriff auf Reflections ausschalten während ich den Code ausführe. Also sowas in der Art:

try {
    setUseReflections(false);
    executePluginCode();
} finally {
    setUseReflections(true);
}

Ich habe null Ahnung von dem SecurityManager. Ich habe mir einige Artikel durchgelesen, kenne die Basics, würde mich jetzt aber nicht ‚Experte‘ nennen. Ich habe viele ähnliche Fragen auf SO gesehen, jedoch wurde dort immer nur die Idee besprochen, wie sowas geht (eben mit einem eigenen SecurityManager), aber ich habe halt keine Ahnung, wie ich sowas umsetzen soll. Mir fehlen da Beispiele, wie andere Leute das machen. Nur hab ich solchen gesehen bzw. gefunden.

Wäre nett, wenn mir jemand helfen könnte.

Nach etwas mehr Google (etwa drei Stunden), habe ich folgendes gefunden:

package de.zerotask.voices.security;

import java.security.AccessControlContext;
import java.security.Permission;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * This class establishes a security manager that confines the permissions for
 * code executed through specific classes, which may be specified by class,
 * class name and/or class loader.
 * <p>
 * To 'execute through a class' means that the execution stack includes the
 * class. E.g., if a method of class {@code A} invokes a method of class
 * {@code B}, which then invokes a method of class {@code C}, and all three
 * classes were previously {@link #confine(Class, Permissions) confined}, then
 * for all actions that are executed by class {@code C} the <i>intersection</i>
 * of the three {@link Permissions} apply.
 * <p>
 * Once the permissions for a class, class name or class loader are confined,
 * they cannot be changed; this prevents any attempts (e.g. of the confined
 * class itself) to release the confinement.
 * <p>
 * Code example:
 * 
 * <pre>
 * Runnable unprivileged = new Runnable() {
 * 	public void run() {
 * 		System.getProperty("user.dir");
 * 	}
 * };
 *
 * // Run without confinement.
 * unprivileged.run(); // Works fine.
 *
 * // Set the most strict permissions.
 * Sandbox.confine(unprivileged.getClass(), new Permissions());
 * unprivileged.run(); // Throws a SecurityException.
 *
 * // Attempt to change the permissions.
 * {
 * 	Permissions permissions = new Permissions();
 * 	permissions.add(new AllPermission());
 * 	Sandbox.confine(unprivileged.getClass(), permissions); // Throws a
 * 															// SecurityException.
 * }
 * unprivileged.run();
 * </pre>
 */
public final class Sandbox {

	private Sandbox() {}

	private static final Map<Class<?>, AccessControlContext>	CHECKED_CLASSES			= Collections
			.synchronizedMap(new WeakHashMap<Class<?>, AccessControlContext>());

	private static final Map<String, AccessControlContext>		CHECKED_CLASS_NAMES		= Collections
			.synchronizedMap(new HashMap<String, AccessControlContext>());

	private static final Map<ClassLoader, AccessControlContext>	CHECKED_CLASS_LOADERS	= Collections
			.synchronizedMap(new WeakHashMap<ClassLoader, AccessControlContext>());

	static {

		// Install our custom security manager.
		if (System.getSecurityManager() != null) {
			throw new ExceptionInInitializerError("There's already a security manager set");
		}
		System.setSecurityManager(new SecurityManager() {

			@Override
			public void checkPermission(Permission perm) {
				assert perm != null;

				for (Class<?> clasS : this.getClassContext()) {

					// Check if an ACC was set for the class.
					{
						AccessControlContext acc = Sandbox.CHECKED_CLASSES.get(clasS);
						if (acc != null)
							acc.checkPermission(perm);
					}

					// Check if an ACC was set for the class name.
					{
						AccessControlContext acc = Sandbox.CHECKED_CLASS_NAMES.get(clasS.getName());
						if (acc != null)
							acc.checkPermission(perm);
					}

					// Check if an ACC was set for the class loader.
					{
						AccessControlContext acc = Sandbox.CHECKED_CLASS_LOADERS
								.get(clasS.getClassLoader());
						if (acc != null)
							acc.checkPermission(perm);
					}
				}
			}
		});
	}

	// --------------------------

	/**
	 * All future actions that are executed through the given {@code clasS} will
	 * be checked against the given {@code
	 * accessControlContext}.
	 *
	 * @throws SecurityException
	 *             Permissions are already confined for the {@code clasS}
	 */
	public static void confine(Class<?> clasS, AccessControlContext accessControlContext) {

		if (Sandbox.CHECKED_CLASSES.containsKey(clasS)) {
			throw new SecurityException(
					"Attempt to change the access control context for '" + clasS + "'");
		}

		Sandbox.CHECKED_CLASSES.put(clasS, accessControlContext);
	}

	/**
	 * All future actions that are executed through the given {@code clasS} will
	 * be checked against the given {@code
	 * protectionDomain}.
	 *
	 * @throws SecurityException
	 *             Permissions are already confined for the {@code clasS}
	 */
	public static void confine(Class<?> clasS, ProtectionDomain protectionDomain) {
		Sandbox.confine(clasS,
				new AccessControlContext(new ProtectionDomain[] { protectionDomain }));
	}

	/**
	 * All future actions that are executed through the given {@code clasS} will
	 * be checked against the given {@code
	 * permissions}.
	 *
	 * @throws SecurityException
	 *             Permissions are already confined for the {@code clasS}
	 */
	public static void confine(Class<?> clasS, Permissions permissions) {
		Sandbox.confine(clasS, new ProtectionDomain(null, permissions));
	}

	// Code for 'CHECKED_CLASS_NAMES' and 'CHECKED_CLASS_LOADERS' omitted here.

}

Benutzt wird das so:

Runnable run = () -> System.getProperty("user.dir)";
// nichts erlauben
Sandbox.confine(run.getClass(), new Permissions());
run.run(); // wird securityexception!

Haltet ihr den Code für sicher? Um also Reflections auszuschalten, müsste ich dann nur einfach die ‘ReflectPermission’ nicht hinzufügen und damit sollte dann kein Zugriff mehr auf private, package und protected mehr erlaubt sein.

wie steht es, und sei es allein nur um die Grenzen abzustecken, um den einfachen Fall aus
java - Reflection Security - Stack Overflow
?

einfach nur
System.setSecurityManager(new SecurityManager());
dann kann bei mir setAccessible() nicht mehr ausgeführt werden
(access denied (“java.lang.reflect.ReflectPermission” “suppressAccessChecks”) )


mit Zurückschrauben wird es freilich komplizierter,
zurecht ist ein neuer setSecurityManager()-Aufruf verboten, sonst könnte das ja auch der böse Code machen…

außerdem allgemein gefährlich im SecurityManager irgendwas zu machen,
ein Logging kann eine schöne Endlosschleife geben wenn Logging-Klasse bis dahin noch nicht geladen, was selber SecurityManager berührt…

ich habe grob in einem eigenen SecurityManager mit

    public void checkPermission(Permission _perm)
    {
        if ("setSecurityManager".equals(_perm.getName()))
        {
            Class[] cc = getClassContext();
            if (cc.length > 3 && cc[3] == Test2.class && cc[2] == System.class) return;
        }
        super.checkPermission(_perm);
    }

das setSecurityManager() von meiner Originalklasse Test2 erlaubt…,
ist so natürlich anfällig falls sich die internen Abläufe in System.setSecurityManager() ändern sollten

Ich halte den Ansatz für falsch und auch nicht Durchführbar (SlaterB hat schon geschrieben warum). Besser wäre, Reflection nur für die Teile des Codes zu erlauben, die sicher sind. Die Erlaubnis lässt sich beim Setzen der Permission bspw. per CodeBase-Attribut auf von dir als sicher eingestufte jars einschränken.

Jetzt nicht böse sein, aber irgendwie ist das genau das selbe (aus meiner Sicht). Logisch ist ja, dass ich meinen Code als sicher empfinde und daher Reflection erlauben würde. Da für mich der Code, den ich als Plugin lade, unsicher ist, würde ich da keine Reflection erlauben wollen.

Da ich halt wenig vom SecurityManager verstehe, fällt mir der Satz hier schwer. Tut mir leid. :frowning:

Seid nicht böse auf mich, aber ich versteh halt wenig von der Java Security. Daher sehe ich in dem Code, den ich gefunden habe, nicht die Probleme. Könntet ihr mir die genauer erläutern? Warum genau das keine gute Idee ist. Was genau daran nicht gut ist. Das wäre mega nett von euch :slight_smile:

ich persönlich habe keine Kritik daran geäußert, nur noch auf einen anderen Code hingewiesen :wink:

aber wenn andere sich noch detaillierter äußern können, dann gut für dich

edit: späte Ergänzung zu meinem Mini-SecurityManager: auf den hat man selber programmiert natürlich auch vollen Zugriff von eigener Klasse aus,
statt kompliziert setSecurityManager-Zugriff abzufangen könnte der auch einfach einen boolean an/ aus bekommen…,
sofern nicht auch wiederum für andere möglich…

Es ist aber von den Machern leider so gedacht, dass bei laufendem SecurityManager Erlaubnisse erteilt werden müssen. Es gibt keine Möglichkeit, Verbote auszusprechen. Deswegen muss man es so rum machen, wie ich geschrieben habe.

Der Security Manager ist eine Infrastruktur zur Absicherung von Code, der in der VM abläuft. Es werden extern statische Regeln definiert, die für den in der VM ablaufenden Code gelten sollen. Wenn es einfach möglich wäre, dass Code (der ja kontrolliert werden soll) hier Regeln ändert, würde es das Konstrukt etwas ad absurdum führen. Stell Dir vor, Du hast eine Firewall und alle beteiligten Hosts können da Freischaltungen an und aus machen, weil sie das grad brauchen. Der der Kontrolliert wird, darf nicht einfach die Kontrollmechanismen ändern, ist doch logisch :-).

Das heißt, sowas wäre dann nur über die Policy-File änderbar oder? Also sozusagen bevor die JVM startet. Oder verstehe ich das falsch?

*** EDIT ***
Angenommen ich installiere einen SecurityManager, einfach den normalen, ohne was zu ändern, müssten dann nicht alle DI Frameworks am Versuch scheitern, irgendwas zu injizieren? :slight_smile:

mehr zur Ergänzung statt konkrete Aussage mal wieder ein Link, wo durchaus paar komplizierte Rechte dazu aufgeschrieben sind… (2011)
Enable securitymanager for Spring and Hibernate - Sevagas

Hab ich ich mir gedacht, das kann man nicht einfach so umgehen :slight_smile: Das heißt wenn man den SecurityManager setzt in tomcat zum Beispiel und die Policy-File ändert, sodass hibernate zum Beispiel keine ReflectPermission kriegt, würde der Server einfach dicht machen oder?

Genau so isses! Das Policy-File ist der Weg, die Permissions zu setzen.

Das kommt sehr darauf an, wie diese arbeiten. Wenn sie Reflection verwenden, um bspw. die Sichtbarkeit von Feldern zu ändern, um dort ewtas injizieren zu können, dann ginge das nicht mehr. Wenn sie das über Bytecode-Manipulation machen, dann brauchen sie keine Reflection.

So isses. Der Tomcat hat übrigens bereits ein Policy-File, das man gut als Ausgangspunkt für Ergänzungen nehmen kann. Ich habe mir den Spaß gemacht, mit enabeltem Security Manager zu starten. Dabei habe ich den Flag -Djava.security.debug=all gesetzt. Erklärung der Flags hier (Troubleshooting Security). Wenn die Ausgabe von all Dich erschlägt, wechsele auf Access. Da kommen dann MB-Weise Logausgaben. Aber ein paar Stunden später hatte ich dann alle Permissions zusammen. Mein Tipp: Nicht zu feingranular werden. Lieber global Permissions Code aus jars zuordnen.

[OT]Die größte “Sünde” hat übrigens jog4j begangen. Dass definiert(e) damals tatsächlich eine Klasse im Package java.lang![/OT]