Comment limiter setAccessible aux seules utilisations «légitimes»?


100

Plus j'en ai appris sur le pouvoir java.lang.reflect.AccessibleObject.setAccessible, plus je suis étonné de ce qu'il peut faire. Ceci est adapté de ma réponse à la question ( Utilisation de la réflexion pour changer le fichier final statique File.separatorChar pour les tests unitaires ).

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"
   }
}

Vous pouvez faire des choses vraiment scandaleuses:

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"
   }
}

Vraisemblablement, les concepteurs d'API réalisent à quel point il setAccessiblepeut être abusif , mais doivent avoir admis qu'il a des utilisations légitimes pour le fournir. Donc mes questions sont:

  • Quelles sont les utilisations vraiment légitimes setAccessible?
    • Java a-t-il été conçu pour ne PAS avoir ce besoin en premier lieu?
    • Quelles seraient les conséquences négatives (le cas échéant) d'une telle conception?
  • Pouvez-vous limiter setAccessibleuniquement les utilisations légitimes?
    • Est-ce seulement à travers SecurityManager?
      • Comment ça marche? Liste blanche / liste noire, granularité, etc.?
      • Est-il courant de devoir le configurer dans vos applications?
    • Puis-je écrire mes cours pour être à l' setAccessibleépreuve quelle que soit la SecurityManagerconfiguration?
      • Ou suis-je à la merci de celui qui gère la configuration?

Je suppose qu'une autre question importante est: DOIS-JE M'INQUIÉTER DE CELA ???

Aucune de mes classes n'a le moindre semblant de confidentialité exécutoire. Le modèle singleton (mettant de côté les doutes sur ses mérites) est désormais impossible à appliquer. Comme le montrent mes extraits ci-dessus, même certaines hypothèses de base sur le fonctionnement de Java fondamental ne sont même pas proches d'être garanties.

CES PROBLÈMES NE SONT-ILS PAS RÉELS ???


D'accord, je viens de confirmer: grâce à setAccessible, les chaînes Java ne sont PAS immuables.

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!"
   }
}

Suis-je le seul à penser que c'est une énorme préoccupation?


30
Vous devriez voir ce qu'ils peuvent faire en C ++! (Oh, et probablement C #.)
Tom Hawtin - tackline

Réponses:


105

Dois-je m'inquiéter à ce sujet ???

Cela dépend entièrement des types de programmes que vous écrivez et du type d'architecture.

Si vous distribuez un composant logiciel appelé foo.jar aux gens du monde entier, vous êtes de toute façon complètement à leur merci. Ils pourraient modifier les définitions de classe à l'intérieur de votre .jar (par ingénierie inverse ou manipulation directe de bytecode). Ils pourraient exécuter votre code dans leur propre JVM, etc. Dans ce cas, vous inquiéter ne vous servira à rien.

Si vous écrivez une application Web qui ne s'interface qu'avec des personnes et des systèmes via HTTP et que vous contrôlez le serveur d'applications, ce n'est pas non plus un problème. Bien sûr, les codeurs de votre entreprise peuvent créer du code qui rompt votre modèle singleton, mais seulement s'ils le souhaitent vraiment.

Si votre futur travail consiste à écrire du code chez Sun Microsystems / Oracle et que vous êtes chargé d'écrire du code pour le noyau Java ou d'autres composants de confiance, vous devez en être conscient. S'inquiéter, cependant, ne fera que vous faire perdre vos cheveux. Dans tous les cas, ils vous feront probablement lire les directives de codage sécurisé ainsi que la documentation interne.

Si vous allez écrire des applets Java, vous devez être conscient du cadre de sécurité. Vous constaterez que les applets non signées qui tentent d'appeler setAccessible entraîneront simplement une SecurityException.

setAccessible n'est pas la seule chose qui contourne les contrôles d'intégrité conventionnels. Il existe une classe Java de base non API appelée sun.misc.Unsafe qui peut faire à peu près tout ce qu'elle veut, y compris accéder directement à la mémoire. Le code natif (JNI) peut également contourner ce type de contrôle.

Dans un environnement sandbox (par exemple, applets Java, JavaFX), chaque classe dispose d'un ensemble d'autorisations et l'accès aux implémentations non sécurisées, setAccessible et natives est contrôlé par SecurityManager.

"Les modificateurs d'accès Java ne sont pas destinés à être un mécanisme de sécurité."

Cela dépend beaucoup de l'endroit où le code Java est exécuté. Les classes Java de base utilisent des modificateurs d'accès comme mécanisme de sécurité pour appliquer le bac à sable.

Quelles sont les utilisations vraiment légitimes de setAccessible?

Les classes Java Core l'utilisent comme un moyen facile d'accéder à des éléments qui doivent rester privés pour des raisons de sécurité. Par exemple, l'infrastructure de sérialisation Java l'utilise pour appeler des constructeurs d'objets privés lors de la désérialisation d'objets. Quelqu'un a mentionné System.setErr, et ce serait un bon exemple, mais curieusement, les méthodes de classe System setOut / setErr / setIn utilisent toutes du code natif pour définir la valeur du champ final.

Une autre utilisation légitime évidente sont les frameworks (persistance, frameworks web, injection) qui ont besoin de jeter un œil à l'intérieur des objets.

Les débogueurs, à mon avis, ne rentrent pas dans cette catégorie, car ils ne fonctionnent normalement pas dans le même processus JVM, mais plutôt dans l'interface avec la JVM en utilisant d'autres moyens (JPDA).

Java a-t-il été conçu pour ne PAS avoir ce besoin en premier lieu?

C'est une question assez profonde à bien répondre. J'imagine que oui, mais vous devrez ajouter d'autres mécanismes qui pourraient ne pas être tout à fait préférables.

Pouvez-vous limiter setAccessible à des utilisations légitimes uniquement?

La restriction OOTB la plus simple que vous puissiez appliquer est d'avoir un SecurityManager et d'autoriser setAccessible uniquement au code provenant de certaines sources. C'est ce que fait déjà Java - les classes Java standard qui proviennent de votre JAVA_HOME sont autorisées à faire setAccessible, tandis que les classes d'applet non signées de foo.com ne sont pas autorisées à faire setAccessible. Comme cela a été dit précédemment, cette permission est binaire, en ce sens que l'on l'a ou non. Il n'y a aucun moyen évident d'autoriser setAccessible à modifier certains champs / méthodes tout en interdisant d'autres. En utilisant SecurityManager, vous pouvez cependant interdire aux classes de référencer complètement certains packages, avec ou sans réflexion.

Puis-je écrire mes classes pour qu'elles soient définies comme étant à l'épreuve des accès quelle que soit la configuration de SecurityManager? ... Ou suis-je à la merci de celui qui gère la configuration?

Vous ne pouvez pas et vous l'êtes certainement.


«Si vous distribuez un composant logiciel appelé foo.jar aux gens du monde entier, vous êtes de toute façon complètement à leur merci. Ils pourraient modifier les définitions de classe dans votre .jar (par ingénierie inverse ou manipulation directe de bytecode). Ils pourrait exécuter votre code dans leur propre JVM, etc. Dans ce cas, vous ne vous inquiétez pas. " Est-ce toujours le cas? Y a-t-il des conseils pour rendre le falsification du logiciel plus difficile (espérons-le de manière significative)? Il est difficile de simplement jeter les applications de bureau Java par la fenêtre à cause de cela.
Sero

@naaz je dois dire que rien n'a changé. C'est juste que l'architecture travaille contre vous. Tout logiciel fonctionnant sur ma machine et sur lequel j'ai un contrôle total, je peux le modifier. Le moyen de dissuasion le plus puissant pourrait être juridique, mais je ne pense pas que cela fonctionnera pour tous les scénarios. Je pense que les binaires Java sont parmi les plus faciles à inverser, mais les gens expérimentés vont tout altérer. L'obscurcissement aide en rendant difficile les gros changements, mais tout ce qui est booléen (comme une vérification des droits d'auteur) est toujours trivial à contourner. Vous pouvez essayer de placer les éléments clés sur un serveur à la place.
Sami Koivu

Que diriez-vous de denuvo?
Sero

15
  • Quelles sont les utilisations vraiment légitimes setAccessible?

Tests unitaires, internes de la JVM (par exemple, implémentation System.setError(...)), etc.

  • Java a-t-il été conçu pour ne PAS avoir ce besoin en premier lieu?
  • Quelles seraient les conséquences négatives (le cas échéant) d'une telle conception?

Beaucoup de choses seraient inimplémentables. Par exemple, diverses injections de persistance, de sérialisation et de dépendances Java dépendent de la réflexion. Et à peu près tout ce qui repose sur les conventions JavaBeans au moment de l'exécution.

  • Pouvez-vous limiter setAccessibleuniquement les utilisations légitimes?
  • Est-ce seulement à travers SecurityManager?

Oui.

  • Comment ça marche? Liste blanche / liste noire, granularité, etc.?

Cela dépend de l'autorisation, mais je pense que l'autorisation d'utilisation setAccessibleest binaire. Si vous voulez de la granularité, vous devez soit utiliser un chargeur de classe différent avec un gestionnaire de sécurité différent pour les classes que vous souhaitez restreindre. Je suppose que vous pouvez implémenter un gestionnaire de sécurité personnalisé qui implémente une logique plus fine.

  • Est-il courant de devoir le configurer dans vos applications?

Non.

  • Puis-je écrire mes cours pour être à l' setAccessibleépreuve quelle que soit la SecurityManagerconfiguration?
    • Ou suis-je à la merci de celui qui gère la configuration?

Non, vous ne pouvez pas, et oui vous l'êtes.

L'autre alternative est de «faire respecter» cela via des outils d'analyse de code source; par exemple, coutume pmdou findbugsrègles. Ou examen sélectif du code du code identifié par (par exemple) grep setAccessible ....

En réponse au suivi

Aucune de mes classes n'a le moindre semblant de confidentialité exécutoire. Le modèle singleton (mettant de côté les doutes sur ses mérites) est désormais impossible à appliquer.

Si cela vous inquiète, alors je suppose que vous devez vous inquiéter. Mais en réalité, vous ne devriez pas essayer de forcer d' autres programmeurs à respecter vos décisions de conception. Si les gens sont assez stupides pour utiliser la réflexion pour créer gratuitement plusieurs instances de vos singletons (par exemple), ils peuvent vivre avec les conséquences.

D'un autre côté, si vous voulez dire «vie privée» pour englober le sens de la protection des informations sensibles contre la divulgation, vous aboyez le mauvais arbre. La manière de protéger les données sensibles dans une application Java consiste à ne pas autoriser le code non approuvé dans le sandbox de sécurité qui traite les données sensibles. Les modificateurs d'accès Java ne sont pas destinés à être un mécanisme de sécurité.

<Exemple de chaîne> - Suis-je le seul à penser que c'est un problème ÉNORME?

Probablement pas le seul :-). Mais l'OMI, ce n'est pas un problème. Il est admis que le code non approuvé doit être exécuté dans un bac à sable. Si vous avez du code de confiance / un programmeur de confiance faisant des choses comme celle-ci, vos problèmes sont pires que des chaînes mutables de manière inattendue. (Pensez aux bombes logiques, à l'exfiltration de données via des canaux secrets , etc.)

Il existe des moyens de traiter (ou d'atténuer) le problème d'un «mauvais acteur» dans votre équipe de développement ou d'exploitation. Mais ils sont coûteux et restrictifs ... et exagérés pour la plupart des cas d'utilisation.


1
Également utile pour des choses telles que les implémentations de persistance. Reflections n'est pas vraiment utile pour les débogueurs (bien que cela ait été initialement prévu). Vous ne pouvez pas modifier utilement (sous-classe) le SecurityManagerpour cela, car tout ce que vous obtenez est la vérification des autorisations - tout ce que vous pouvez vraiment faire est de regarder l'acc et peut-être de parcourir la pile pour vérifier le thread actuel). grep java.lang.reflect.
Tom Hawtin - Tacle du

@Stephen C: +1 déjà, mais j'apprécierais également votre contribution sur une autre question que je viens d'ajouter. Merci d'avance.
polygenelubricants

Notez que grep est une idée terrible. Il y a tellement de façons d'exécuter dynamiquement du code en Java que vous en manquerez certainement une. Le code de liste noire en général est une recette pour l'échec.
Antimoine

"Les modificateurs d'accès Java ne sont pas destinés à être un mécanisme de sécurité." - en fait, oui ils le sont. Java est conçu pour permettre au code approuvé et non approuvé de coexister dans la même machine virtuelle - cela faisait partie de ses exigences fondamentales depuis le tout début. Mais uniquement lorsque le système fonctionne avec un SecurityManager verrouillé. La possibilité de mettre du code en sandbox dépend entièrement de l'application des spécificateurs d'accès.
Jules

@Jules - La plupart des experts Java ne seraient pas d'accord avec cela; Par exemple, stackoverflow.com/questions/9201603/…
Stephen C

11

La réflexion est en effet orthogonale à la sûreté / sécurité dans cette perspective.

Comment limiter la réflexion?

Java a un gestionnaire de sécurité et ClassLoadercomme base de son modèle de sécurité. Dans votre cas, je suppose que vous devez regarder java.lang.reflect.ReflectPermission.

Mais cela ne résout pas complètement le problème de la réflexion. Les capacités de réflexion disponibles devraient être soumises à un système d'autorisation précis, ce qui n'est pas le cas actuellement. Par exemple, pour autoriser certains framework à utiliser la réflexion (par exemple Hibernate), mais pas le reste de votre code. Ou pour permettre à un programme de refléter uniquement en lecture seule, à des fins de débogage.

Une approche qui pourrait devenir courante à l'avenir est l'utilisation de ce que l'on appelle des miroirs pour séparer les capacités de réflexion des classes. Voir Miroirs: principes de conception pour les installations de méta-niveau . Il existe cependant diverses autres recherches qui abordent ce problème. Mais je conviens que le problème est plus grave pour le langage dynamique que pour les langages statiques.

Doit-on s'inquiéter de la superpuissance que nous donne la réflexion? Oui et non.

Oui dans le sens où la plate-forme Java est censée être sécurisée avec Classloaderun gestionnaire de sécurité. La capacité de jouer avec la réflexion peut être considérée comme une brèche.

Non, dans le sens où la plupart des systèmes ne sont de toute façon pas entièrement sécurisés. Un grand nombre de classes peuvent souvent être sous-classées et vous pourriez déjà abuser du système avec juste cela. Bien sûr, les classes peuvent être faites final, ou scellées afin qu'elles ne puissent pas être sous-classées dans d'autres bocaux. Mais seules quelques classes sont correctement sécurisées (par exemple String) en fonction de cela.

Voir cette réponse sur la classe finale pour une explication intéressante. Voir également le blog de Sami Koivu pour plus de piratage Java autour de la sécurité.

Le modèle de sécurité de Java peut être considéré comme insuffisant à certains égards. Certains langages comme NewSpeak adoptent une approche encore plus radicale de la modularité, où vous n'avez accès qu'à ce qui vous est explicitement donné par l'inversion de dépendances (par défaut rien).

Il est également important de noter que la sécurité est de toute façon relative . Au niveau du langage, vous ne pouvez par exemple pas empêcher une forme de module de consommer 100% du CPU ou de consommer toute la mémoire jusqu'à un OutOfMemoryException. Ces préoccupations doivent être traitées par d’autres moyens. Nous verrons peut-être dans le futur Java étendu avec des quotas d'utilisation des ressources, mais ce n'est pas pour demain :)

Je pourrais développer davantage sur le sujet, mais je pense avoir fait valoir mon point.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.