Je mehr ich über die Kraft von gelernt habe java.lang.reflect.AccessibleObject.setAccessible
, desto mehr staune ich darüber, was sie bewirken kann. Dies wird aus meiner Antwort auf die Frage ( Verwenden von Reflection zum Ändern der statischen endgültigen File.separatorChar für Unit-Tests ) angepasst .
import java.lang.reflect.*;
public class EverythingIsTrue {
static void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
public static void main(String args[]) throws Exception {
setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"
}
}
Sie können wirklich unerhörte Dinge tun:
public class UltimateAnswerToEverything {
static Integer[] ultimateAnswer() {
Integer[] ret = new Integer[256];
java.util.Arrays.fill(ret, 42);
return ret;
}
public static void main(String args[]) throws Exception {
EverythingIsTrue.setFinalStatic(
Class.forName("java.lang.Integer$IntegerCache")
.getDeclaredField("cache"),
ultimateAnswer()
);
System.out.format("6 * 9 = %d", 6 * 9); // "6 * 9 = 42"
}
}
Vermutlich erkennen die API-Designer, wie missbräuchlich sein setAccessible
kann, müssen aber eingeräumt haben, dass es legitime Verwendungszwecke hat, um es bereitzustellen. Meine Fragen sind also:
- Wofür sind die wirklich legitimen Verwendungszwecke
setAccessible
?- Könnte Java so konzipiert sein, dass es dieses Bedürfnis überhaupt nicht hat?
- Was wären die negativen Konsequenzen (wenn überhaupt) eines solchen Designs?
- Können Sie sich nur
setAccessible
auf legitime Verwendungen beschränken?- Ist es nur durch
SecurityManager
?- Wie funktioniert es? Whitelist / Blacklist, Granularität usw.?
- Ist es üblich, dass Sie es in Ihren Anwendungen konfigurieren müssen?
- Kann ich meine Klassen
setAccessible
unabhängig von derSecurityManager
Konfiguration so schreiben, dass sie sicher sind?- Oder bin ich dem ausgeliefert, der die Konfiguration verwaltet?
- Ist es nur durch
Ich denke, eine weitere wichtige Frage ist: MUSS ICH SICH UM DIESE SORGEN ???
Keine meiner Klassen hat den Anschein einer durchsetzbaren Privatsphäre. Das Singleton-Muster (Zweifel an seinen Vorzügen beiseite lassen) kann jetzt nicht mehr durchgesetzt werden. Wie meine obigen Ausschnitte zeigen, sind selbst einige grundlegende Annahmen darüber, wie Java Fundamental funktioniert, nicht annähernd garantiert.
SIND DIESE PROBLEME NICHT WIRKLICH ???
Okay, ich habe gerade bestätigt: Dank setAccessible
sind Java-Strings NICHT unveränderlich.
import java.lang.reflect.*;
public class MutableStrings {
static void mutate(String s) throws Exception {
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
value.set(s, s.toUpperCase().toCharArray());
}
public static void main(String args[]) throws Exception {
final String s = "Hello world!";
System.out.println(s); // "Hello world!"
mutate(s);
System.out.println(s); // "HELLO WORLD!"
}
}
Bin ich der einzige, der das für ein RIESIGES Problem hält?
quelle
Antworten:
Muss ich mir darüber Sorgen machen?
Das hängt ganz davon ab, welche Arten von Programmen Sie schreiben und für welche Art von Architektur.
Wenn Sie eine Softwarekomponente namens foo.jar an die Menschen auf der Welt verteilen, sind Sie sowieso völlig ihrer Gnade ausgeliefert. Sie können die Klassendefinitionen in Ihrer .jar-Datei ändern (durch Reverse Engineering oder direkte Bytecode-Manipulation). Sie könnten Ihren Code in ihrer eigenen JVM usw. ausführen. In diesem Fall nützen Ihnen Sorgen nichts.
Wenn Sie eine Webanwendung schreiben, die nur über HTTP mit Personen und Systemen verbunden ist, und den Anwendungsserver steuern, ist dies ebenfalls kein Problem. Sicher, die anderen Programmierer in Ihrem Unternehmen erstellen möglicherweise Code, der Ihr Singleton-Muster bricht, aber nur, wenn sie es wirklich wollen.
Wenn Ihr zukünftiger Job das Schreiben von Code bei Sun Microsystems / Oracle ist und Sie mit dem Schreiben von Code für den Java-Kern oder andere vertrauenswürdige Komponenten beauftragt sind, sollten Sie sich dessen bewusst sein. Wenn Sie sich jedoch Sorgen machen, verlieren Sie nur Ihre Haare. In jedem Fall werden Sie wahrscheinlich dazu gebracht, die Richtlinien für die sichere Codierung zusammen mit der internen Dokumentation zu lesen .
Wenn Sie Java-Applets schreiben möchten, sollten Sie das Sicherheits-Framework beachten. Sie werden feststellen, dass nicht signierte Applets, die versuchen, setAccessible aufzurufen, nur zu einer SecurityException führen.
setAccessible ist nicht das einzige, was herkömmliche Integritätsprüfungen umgeht. Es gibt eine Nicht-API-Java-Kernklasse namens sun.misc.Unsafe, die so ziemlich alles kann, was sie will, einschließlich des direkten Zugriffs auf den Speicher. Native Code (JNI) kann diese Art der Steuerung ebenfalls umgehen.
In einer Sandbox-Umgebung (z. B. Java Applets, JavaFX) verfügt jede Klasse über eine Reihe von Berechtigungen, und der Zugriff auf Unsafe, setAccessible und definierende native Implementierungen wird vom SecurityManager gesteuert.
"Java-Zugriffsmodifikatoren sind nicht als Sicherheitsmechanismus gedacht."
Das hängt sehr davon ab, wo der Java-Code ausgeführt wird. Die Java-Kernklassen verwenden Zugriffsmodifikatoren als Sicherheitsmechanismus, um die Sandbox zu erzwingen.
Was sind die wirklich legitimen Verwendungszwecke für setAccessible?
Die Java-Kernklassen verwenden es als einfache Möglichkeit, auf Dinge zuzugreifen, die aus Sicherheitsgründen privat bleiben müssen. Das Java Serialization Framework verwendet es beispielsweise zum Aufrufen privater Objektkonstruktoren beim Deserialisieren von Objekten. Jemand erwähnte System.setErr, und es wäre ein gutes Beispiel, aber seltsamerweise verwenden die Systemklassenmethoden setOut / setErr / setIn alle systemeigenen Code zum Festlegen des Werts des letzten Feldes.
Eine weitere offensichtliche legitime Verwendung sind die Frameworks (Persistenz, Web-Frameworks, Injection), die in die Innenseiten von Objekten hineinschauen müssen.
Debugger fallen meiner Meinung nach nicht in diese Kategorie, da sie normalerweise nicht im selben JVM-Prozess ausgeführt werden, sondern die Schnittstelle zur JVM mit anderen Mitteln (JPDA).
Könnte Java so konzipiert sein, dass es dieses Bedürfnis überhaupt nicht hat?
Das ist eine ziemlich tiefe Frage, die gut zu beantworten ist. Ich stelle mir ja vor, aber Sie müssten einige andere Mechanismen hinzufügen, die möglicherweise nicht so vorzuziehen sind.
Können Sie setAccessible nur auf legitime Verwendungen beschränken?
Die einfachste OOTB-Einschränkung, die Sie anwenden können, besteht darin, einen SecurityManager zu haben und setAccessible nur für Code zuzulassen, der aus bestimmten Quellen stammt. Dies ist, was Java bereits tut - die Standard-Java-Klassen, die von Ihrem JAVA_HOME stammen, dürfen setAccessible ausführen, während nicht signierte Applet-Klassen von foo.com setAccessible nicht ausführen dürfen. Wie bereits gesagt, ist diese Erlaubnis binär in dem Sinne, dass man sie entweder hat oder nicht. Es gibt keine offensichtliche Möglichkeit, setAccessible zu erlauben, bestimmte Felder / Methoden zu ändern, während andere nicht zugelassen werden. Mit dem SecurityManager können Sie jedoch verhindern, dass Klassen bestimmte Pakete mit oder ohne Reflexion vollständig referenzieren.
Kann ich meine Klassen unabhängig von der SecurityManager-Konfiguration als setAccessible-Proof schreiben? ... oder bin ich dem ausgeliefert, der die Konfiguration verwaltet?
Sie können nicht und Sie sind es mit Sicherheit.
quelle
Unit-Tests, Interna der JVM (zB Implementierung
System.setError(...)
) und so weiter.Viele Dinge wären nicht umsetzbar. Beispielsweise hängen verschiedene Java-Persistenz-, Serialisierungs- und Abhängigkeitsinjektionen von der Reflexion ab. Und so ziemlich alles, was zur Laufzeit auf den JavaBeans-Konventionen beruht.
Ja.
Es hängt von der Erlaubnis ab, aber ich glaube, dass die Erlaubnis zur Verwendung
setAccessible
binär ist. Wenn Sie Granularität wünschen, müssen Sie entweder einen anderen Klassenlader mit einem anderen Sicherheitsmanager für die Klassen verwenden, die Sie einschränken möchten. Ich denke, Sie könnten einen benutzerdefinierten Sicherheitsmanager implementieren, der eine feinkörnigere Logik implementiert.Nein.
Nein, kannst du nicht und ja, das bist du.
Die andere Alternative besteht darin, dies über Tools zur Analyse des Quellcodes zu "erzwingen". zB Sitte
pmd
oderfindbugs
Regeln. Oder selektive Codeüberprüfung des durch (sagen wir) identifizierten Codesgrep setAccessible ...
.Als Antwort auf das Follow-up
Wenn dich das beunruhigt, dann musst du dir wohl Sorgen machen. Aber eigentlich sollten Sie nicht versuchen, andere Programmierer zu zwingen , Ihre Entwurfsentscheidungen zu respektieren. Wenn Leute dumm genug sind, Reflexion zu verwenden, um (zum Beispiel) mehrere Instanzen Ihrer Singletons unentgeltlich zu erstellen, können sie mit den Konsequenzen leben.
Wenn Sie dagegen "Datenschutz" meinen, um die Bedeutung des Schutzes sensibler Informationen vor Offenlegung zu erfassen, bellen Sie den falschen Baum an. Der Schutz vertraulicher Daten in einer Java-Anwendung besteht nicht darin, nicht vertrauenswürdigen Code in die Sicherheits-Sandbox zuzulassen, die vertrauliche Daten verarbeitet. Java-Zugriffsmodifikatoren sind nicht als Sicherheitsmechanismus gedacht.
Wahrscheinlich nicht der einzige :-). Aber IMO, das ist kein Problem. Es wird akzeptiert, dass nicht vertrauenswürdiger Code in einer Sandbox ausgeführt werden sollte. Wenn Sie vertrauenswürdigen Code / einen vertrauenswürdigen Programmierer haben, der solche Dinge tut, sind Ihre Probleme schlimmer als unerwartet veränderbare Zeichenfolgen. (Denken Sie an Logikbomben, Exfiltration von Daten über verdeckte Kanäle usw.)
Es gibt Möglichkeiten, das Problem eines "schlechten Akteurs" in Ihrem Entwicklungs- oder Betriebsteam zu lösen (oder zu mildern). Aber sie sind teuer und restriktiv ... und für die meisten Anwendungsfälle übertrieben.
quelle
SecurityManager
, da Sie nur die Berechtigungsprüfung erhalten - alles, was Sie wirklich tun können, ist, sich die acc anzuschauen und möglicherweise den Stapel zu durchlaufen, um den aktuellen Thread zu überprüfen.grep java.lang.reflect.
Reflexion ist unter dieser Perspektive tatsächlich orthogonal zu Sicherheit.
Wie können wir die Reflexion begrenzen?
Java hat einen Sicherheitsmanager und
ClassLoader
als Grundlage für sein Sicherheitsmodell. In Ihrem Fall müssen Sie sich wohl umsehenjava.lang.reflect.ReflectPermission
.Dies löst das Problem der Reflexion jedoch nicht vollständig. Die verfügbaren Reflexionsfähigkeiten sollten einem feinkörnigen Autorisierungsschema unterliegen, was derzeit nicht der Fall ist. ZB, um bestimmten Frameworks die Verwendung von Reflection zu ermöglichen (z. B. Hibernate), aber nicht den Rest Ihres Codes. Oder zuzulassen, dass ein Programm zum Debuggen nur schreibgeschützt wiedergegeben wird.
Ein Ansatz, der in Zukunft zum Mainstream werden könnte, ist die Verwendung sogenannter Spiegel , um Reflexionsfähigkeiten von Klassen zu trennen. Siehe Spiegel: Entwurfsprinzipien für Einrichtungen auf Metaebene . Es gibt jedoch verschiedene andere Untersuchungen , die sich mit diesem Problem befassen . Ich stimme jedoch zu, dass das Problem für dynamische Sprachen schwerwiegender ist als für statische Sprachen.
Sollten wir uns Sorgen um die Supermacht machen, die uns das Nachdenken gibt? Ja und nein.
Ja in dem Sinne, dass die Java-Plattform mit einem
Classloader
Sicherheitsmanager gesichert werden soll . Die Fähigkeit, sich mit Reflexion zu beschäftigen, kann als Verstoß angesehen werden.Nein in dem Sinne, dass die meisten Systeme sowieso nicht ganz sicher sind. Viele Klassen können häufig in Unterklassen unterteilt werden, und Sie könnten das System möglicherweise bereits damit missbrauchen. Natürlich können Klassen gemacht
final
oder versiegelt werden, so dass sie nicht in andere Gläser unterteilt werden können. Demnach sind aber nur wenige Klassen korrekt gesichert (zB String).In dieser Antwort zur Abschlussklasse finden Sie eine schöne Erklärung. Siehe auch den Blog von Sami Koivu für mehr Java-Hacking rund um die Sicherheit.
Das Sicherheitsmodell von Java kann in gewisser Hinsicht als unzureichend angesehen werden. Einige Sprachen wie NewSpeak verfolgen einen noch radikaleren Ansatz in Bezug auf Modularität, bei dem Sie nur auf das zugreifen können, was Ihnen durch Abhängigkeitsinversion explizit gegeben wird (standardmäßig nichts).
Es ist auch wichtig zu beachten, dass die Sicherheit ohnehin relativ ist . Auf Sprachebene können Sie beispielsweise nicht verhindern, dass eine Modulform 100% der CPU oder den gesamten Speicher bis zu a verbraucht
OutOfMemoryException
. Solche Bedenken müssen auf andere Weise ausgeräumt werden. Wir werden vielleicht in Zukunft sehen, dass Java um Ressourcenauslastungsquoten erweitert wird, aber es ist nicht für morgen :)Ich könnte das Thema näher erläutern, aber ich glaube, ich habe meinen Standpunkt klargestellt.
quelle