Wie kann man setAccessible nur auf "legitime" Verwendungen beschränken?

100

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 setAccessiblekann, 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 setAccessibleauf 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 setAccessibleunabhängig von der SecurityManagerKonfiguration so schreiben, dass sie sicher sind?
      • Oder bin ich dem ausgeliefert, der die Konfiguration verwaltet?

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 setAccessiblesind 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?

Polygenschmierstoffe
quelle
30
Sie sollten sehen, was sie in C ++ tun können! (Oh, und wahrscheinlich C #.)
Tom Hawtin - Tackline

Antworten:

105

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.

Sami Koivu
quelle
"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) könnte Ihren Code in einer eigenen JVM usw. ausführen. In diesem Fall nützt es Ihnen nichts, wenn Sie sich Sorgen machen. " Ist das noch der Fall? Irgendwelche Ratschläge, um die Manipulation von Software zu erschweren (hoffentlich erheblich)? Aus diesem Grund ist es schwierig, Java-Desktop-Anwendungen einfach aus dem Fenster zu werfen.
Sero
@naaz Ich muss sagen, nichts hat sich geändert. Es ist nur so, dass die Architektur gegen Sie arbeitet. Jede Software, die auf meinem Computer ausgeführt wird und über die ich die volle Kontrolle habe, kann ich ändern. Die stärkste Abschreckung mag eine legale sein, aber ich kann mir nicht vorstellen, dass dies für alle Szenarien funktioniert. Ich denke, Java-Binärdateien gehören zu den am einfachsten umkehrbaren, aber erfahrene Leute werden alles manipulieren. Die Verschleierung hilft, indem sie es schwierig macht, große Änderungen vorzunehmen, aber alles, was boolesch ist (wie eine Urheberrechtsprüfung), ist immer noch trivial zu umgehen. Sie können stattdessen versuchen, wichtige Teile auf einem Server abzulegen.
Sami Koivu
Wie wäre es mit Denuvo?
Sero
15
  • Wofür sind die wirklich legitimen Verwendungszwecke setAccessible?

Unit-Tests, Interna der JVM (zB Implementierung System.setError(...)) und so weiter.

  • 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?

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.

  • Können Sie sich nur setAccessibleauf legitime Verwendungen beschränken?
  • Ist es nur durch SecurityManager?

Ja.

  • Wie funktioniert es? Whitelist / Blacklist, Granularität usw.?

Es hängt von der Erlaubnis ab, aber ich glaube, dass die Erlaubnis zur Verwendung setAccessiblebinä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.

  • Ist es üblich, dass Sie es in Ihren Anwendungen konfigurieren müssen?

Nein.

  • Kann ich meine Klassen setAccessibleunabhängig von der SecurityManagerKonfiguration so schreiben, dass sie sicher sind?
    • Oder bin ich dem ausgeliefert, der die Konfiguration verwaltet?

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 pmdoder findbugsRegeln. Oder selektive Codeüberprüfung des durch (sagen wir) identifizierten Codes grep setAccessible ....

Als Antwort auf das Follow-up

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.

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.

<String-Beispiel> - Bin ich der einzige, der dies für ein RIESIGES Problem hält?

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.

Stephen C.
quelle
1
Auch nützlich für Dinge wie Persistenzimplementierungen. Reflexionen sind für Debugger nicht wirklich nützlich (obwohl dies ursprünglich beabsichtigt war). Sie können dies nicht sinnvoll ändern (Unterklasse) 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.
Tom Hawtin - Tackline
@Stephen C: +1 bereits, aber ich würde mich auch über Ihre Eingabe zu einer weiteren Frage freuen, die ich gerade hinzugefügt habe. Danke im Voraus.
Polygenelubricants
Beachten Sie, dass grep eine schreckliche Idee ist. Es gibt so viele Möglichkeiten, Code in Java dynamisch auszuführen, dass Sie mit ziemlicher Sicherheit eine verpassen werden. Das Auflisten von Code im Allgemeinen ist ein Rezept für einen Fehler.
Antimon
"Java-Zugriffsmodifikatoren sind nicht als Sicherheitsmechanismus gedacht." - Eigentlich ja. Java wurde entwickelt, um zu ermöglichen, dass vertrauenswürdiger und nicht vertrauenswürdiger Code in derselben virtuellen Maschine koexistiert - dies war von Anfang an Teil seiner Kernanforderungen. Aber nur, wenn das System mit einem gesperrten SecurityManager ausgeführt wird. Die Fähigkeit zum Sandbox-Code hängt vollständig von der Durchsetzung der Zugriffsspezifizierer ab.
Jules
@ Jules - Die meisten Java-Experten würden dem nicht zustimmen. zB stackoverflow.com/questions/9201603/…
Stephen C
11

Reflexion ist unter dieser Perspektive tatsächlich orthogonal zu Sicherheit.

Wie können wir die Reflexion begrenzen?

Java hat einen Sicherheitsmanager und ClassLoaderals Grundlage für sein Sicherheitsmodell. In Ihrem Fall müssen Sie sich wohl umsehen java.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 ClassloaderSicherheitsmanager 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 finaloder 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.

ewernli
quelle