lundi 2 avril 2012

Android : Code Securité

 Dans ce tutorial nous allons aborder quelques bonne pratiques de développement en Java dans le cadre d'un code solide et sécurisé.  Des règles fondamentales que tout développeur Java qui se respecte doit se forcer de  respecter .Ces règles peuvent apparaître un peu technique voir même avancé pour des développeurs de rang.
Bien sûr, la sécurité à l’état parfait n’existe pas et le but de ces règles n'est pas de fournir un code sécurisé à 100%  mais d'éliminer certains types d'attaques élémentaires.
Ces règles s'appliquent à Android nativement.


Ne comptez pas sur l'initialisation

La plupart des développeurs de rang pense qu'il n'y a pas  un moyen d'allouer un objet sans faire appel à son constructeur. Ils se trompent : il y’a plusieurs façons d'allouer des objets non initialisés.
La problématique ici est qu'un mal-intentionné peut créer des instances de votre classe sans passer par le constructeur( à partir de la sérialisation, clonage...) et profiter de toutes les méthodes de votre classes.
La façon facile de se protéger contre ce problème est d'écrire vos classes de sorte que, avant toute opération sur un object, vérifier qu'il a été initialisé. Vous pouvez procéder comme suit:
  •  Faire toutes les variables privés sans excuses. Et dans le cas que vous voulez accéder depuis l’extérieur, alors créer des setters et getter selon la nécessité. Ne générer jamais automatiquement tous les setters et getters sans sentir réellement le besoin. Beaucoup d’entre nous génèrent directement depuis leur IDE des setters et getter alors qu’ils n’en n’ont pas tous besoin, surtout des setters. Penser à minimiser au plus possible l'accès  à votre classes et à vos objects. 
  •   Ajouter une nouvelle variable private booléen, appelé init, à chaque instantiation  demandez à chaque constructeur de définir la variable init comme sa dernière action. Dans chaque autre methode  vérifier que init est true, avant de faire quoi que ce soit.  Cette action peut paraitre un peu dérangeant mais ca permet de produire un code de qualité et très sure..
  • Appliquer cette variable init dans le cas aussi d'une utilisaton statique ( la variable init sera alors statique)
  • N’appelez jamais une method non-final dans le constructeur. Appéler une methode non final dans une instantiation peut introduire une  dependance  imprévisible  de l’état initial de votre object avec une sous-class qui implemente cette methode.
 L'utilité de la variable init c'est  de vous assurer qu'un tel object a réellement le droit d'utiliser vos méthodes publiques.

Limiter l'accès à vos classes, méthodes et variables

Chaque classe, chaque méthode ou chaque variable qui n'est pas private est un point faible de la sécurité et fournit un point d'entrée potentiel pour un mal-intentionné.
Par défaut, tout doit être private. Faire quelque chose de non-private seulement s' il y’a une nécessité et commenter la raison.

Rendez tous vos object immutable sauf nécéssité

Avant d'entrer dans le vif du sujet, je vous proposerai une idée sur les object immutables:
Un objet est considéré comme immuable, si son état ne peut pas changer après son instantiation. L'utilisation maximale des object immutables  est largement encouragée  comme une stratégie d'un code solide,fiable et simple.
Les programmeurs sont souvent réticents à employer des objets immuables,parce qu'ils s'inquietent  du coût de la création d'un nouvel objet, par opposition à la mise à jour d'un objet en place. L'impact de la création d'objet est souvent surestimée, et peut être compensée par quelques  gains d'efficacité associés à des objets immuables. 
Les objets immutables sont particulièrement utiles dans les applications concurrentes. Comme ils ne peuvent pas changer d'état, ils ne peuvent pas être corrompu  dans les états. Il s'agit d'une bonne règle de codage afin de réduire le nombre de variables modifiables autant que possible. En outre, suivant le principe de la conception,  l'utilisation d'objets immuables est également un avantage par rapport à des problèmes de synchronisation. Ne jamais enregistrer les références à des objets mutables dans une variable membre, si une telle référence est un paramètre d'un constructeur public
Une condition nécessaire pour la programmation de logiciel sécurisé est la mise en œuvre claire et stricte de l'encapsulation. La possibilité de modifier les variables membres d'un objet de l'extérieur représente une violation grave de cette règle de base.
Les critères suivantes définissent une technique  simple pour créer des objets immuables.Toutes les classes "dites" immuable» ne suivent pas ces regles.Toutefois, ces stratégies nécessitent une analyse sophistiquée et ne sont pas pour les "developpeur de rang".
  • Déclarer tout les attributs private final
  • Ne pas  laisser  les sous-classes implementer les  méthodes en declarant les methodes simplement finale.  La façon la plus simple de le faire est de déclarer la classe comme "final" pour éviter tout héritage Une approche plus sophistiquée consiste à rendre le constructeur privé et construire des instances dans les Factory.
  •  Si lun attribut est un object mutable ou fait reference à un object mutable ou contient des object mutables ;ne pas permettre ces object d'etre modifiés  et ne pas fournir des méthodes qui modifient ces objets mutables.
Bien sure il y'a d'autre technique plus avancés ou élémentaires pour créer des object immutables...

Faire tout final.

Si une classe ou une méthode est non-final, un mal-intentionné pourrait tenter de l'étendre d'une manière dangereuse et imprévisible. Par défaut, tout devrait être final. Faire quelque chose de non-final que si il y’a une bonne raison, et  documenter la raison. Généralement dans notre métier de développeurs nous employons rarement final devant les méthodes et classes alors qu’il est évident qu’on a pas besoin d’étendre dans plus de 80% de cas. Ce conseil peut sembler sévère. Après tout, la règle est pour vous demander de renoncer à l'extensibilité, qui est l'un des principaux avantages  d'un langage orienté objet comme Java. Lorsque vous essayez d'assurer la sécurité, cependant, l'extensibilité est votre ennemi; elle fournit à un mal-intentionné  des  moyens pour causer des problèmes. Donc c'est à vous de voire la nécessité.
Une meilleur caracteristique de l'environnement Java est sa capacité à charger dynamiquement des classes. Nécessairement, cette flexibilité a un prix, y compris un modèle de sécurité plus complexe. Si les classes peuvent être chargées dynamiquement, à tout moment, la machine virtuelle doit être en mesure d'appliquer des politiques de sécurité sur le code en cours d'exécution. Les class final sont utilisées dans ce contexte à empêcher le code malveillant de modifier la sémantique des classes tout au long du processus.
Le meilleur exemple connu d'une classe final est certainement java.lang.String . Cette classe est si vital pour le fonctionnement du compilateur Java et interprète qu'il doit être garanti que chaque fois que le code utilise une chaîne, il devient exactement un java.lang.String et non une instance d'une autre classe. 

Ne comptez pas sur la portée  package

Vous pourriez penser que vous pouvez empêcher d'étendre votre classe ou de ses méthodes, en déclarant la classe non-public par ce qu’il ne pourra pas acceder à votre class dans votre package. Les classes, les méthodes et les variables qui ne sont pas explicitement marqués comme  private sont accessibles dans le même package. Ne vous fiez pas à ce sujet pour la sécurité. Les packages  Java ne sont pas fermés, de sorte qu'un attaquant pourrait introduire une nouvelle classe à l'intérieur de votre package, et d'utiliser cette nouvelle classe d'accéder à des choses qu’on pensait cacher. Pour l'information seulement quelques packages en Java sont fermés par défaut tel que java lang. Surement il existes des JVM qui permettent de fermer vos propres packages. Cependant c’est toujours une précaution en terme de développeur dans un environnement « sécurisé » de ne pas laisser votre « sécurité dans la main de la JVM.

 Ne pas utiliser les classes internes

Nos traditionnels cours académiques nous enseignent que les classes internes ne sont pas accessibles que par les classes qui les englobent. En fait c’est pas le cas pour le bytecode. En fait le bytecode Java n’a pas de concept de classes internes. Donc les classes internes sont traduites par les compilateurs comme les classes ordinaires qui se trouvent accessible à tout le package or nous venons de voir qu’il ne faut pas compter sur «la portée package » en Java.
Et ce n’est pas tout : Une classe interne possède des  accès aux champs de la classe qui l’entourent même s’ils sont private (c’est ça un des intérêts de classes internes) même si ces champs sont déclarés private. Et comme la classe interne est traduite en une classe normal dans le même package que la classe l’englobant. et i afin de permettre à la classe interne d’accéder toujours aux attributs private de la classe englobant, le compilateur change silencieusement la portée des variables private en porté package ! C'est déjà suffisamment grave que la classe interne est exposée, mais c'est encore pire que le compilateur est en mode silencieux de passer votre décision de transformer les variables privates en default sans votre consentement ! Ne le permettez pas

Faites vos classes Uncloneable

La méthode clone() de Java peut permettre à un attaquant de fabriquer de nouvelles instances des classes que vous définissez, sans exécuter  faire appel à votre constructeur. Si votre classe n'est pas clonable, on peut définir une sous-classe de votre classe, et de faire appliquer la sous-classe java.lang.Cloneable. Cela permet à l'attaquant de faire de nouvelles instances de votre classe. Les nouvelles instances sont fait en copiant les images de la mémoire des objets existants, bien que cela est parfois une façon acceptable de faire un nouvel objet, il ne l'est pas souvent
Plutôt que de s'inquiéter à ce sujet, il sera mieux de rendre vos object non-clonable  en implementant la methode clone de la class Object.
Si vous voulez que votre classe soit clonable, et que vous avez examiné les conséquences de ce choix, alors vous pouvez toujours vous protéger. Si vous définissez une méthode clone vous-même, le rendre final comme le suivant :

@Override
      public final Object clone() throws CloneNotSupportedException {

            throw new CloneNotSupportedException("Clone is not supported");
     
    }


Faites votre classe non sérialisable

La sérialisation est dangereuse, car elle permet à un mal-intentionné de mettre la main sur l'état interne de vos objets. Un mal-intentionné peut sérialiser un de vos objets dans un tableau d'octets qui peut être lu. Cela permet à l'adversaire d'inspecter l'état interne complet de votre objet, y compris tous les champs que vous avez marqués private ainsi que l'état interne de tous les objets que vous référencez.
Pour éviter cela, vous pouvez rendre  votre objet impossible à sérialiser. Pour cela il suffit de  déclarer la méthode writeObject. Cette méthode est déclarée final de sorte qu'une sous-classe définie par l'adversaire ne peut pas l'implementer. Ou bien definir simplement tous vos attributs "transicent"


private final void writeObject()throws IOException{

            throw new IOException(" Not Serializable");
      }

Dans le cas de la nécessité, n’oubliez pas le mot clé transcient pour isoler les variables que vous ne voulez pas sérialiser.

Faites votre  class non-déserializeable

Cette règle est encore plus important que le précédent. Même si votre classe n'est pas sérializeable, il peut  être déserializeable. Un mal-intentionné peut créer une séquence d'octets qui arrive à désérialiser une instance de votre classe. Ceci est dangereux, car vous n'avez pas de contrôle sur  l'état de l'objet désérialisé.  Vous pouvez penser que la désérialisation d'un autre type de constructeur public pour votre objet, mais malheureusement, il est une sorte de constructeur qui est difficile pour vous de contrôler. Vous pouvez éviter ce genre d'attaque en le rendant impossible de désérialiser un flux d'octets en une instance de votre classe. Vous pouvez le faire en déclarant la méthode readObject:


  private final  void readObject(ObjectInputStream in)throws IOException {

     throw new java.io.IOException ("Deserialization is not   supported");
     
}

Eviter la comparaison par nom

Parfois, vous voulez comparer les classes de deux objets pour voir si elles sont les mêmes, ou si vous voulez voir si un objet a une classe particulière. Lorsque vous faites cela, vous devez être conscient qu'il peut y avoir plusieurs classes avec le même nom dans une JVM. C'est une erreur de comparer les classes par leur nom puisque deux classes différentes peuvent avoir le même nom. Une meilleure façon est de comparer les objets de classe pour l'égalité directement. Je vous laisse deviner en quoi celle ci- peut être dangereuse!

Exception :

Java fournit un cadre confortable pour la gestion des erreurs avec sa classe Exception. Mais éviter d’employer try catch avec une catch vide. Les exceptions ne doivent jamais être catché sous silence par exemple en ajoutant la methode printStackTrace. Ceci est particulièrement important si vous attrapez des exceptions générales. Si vous utiliser des ressources lourdes dans try, utiliser toujours finally pour liberer les ressources qui ont été consommées dans le bloc try.
Si les ressources système sont allouées dans un bloc try, il doit être garanti que ces ressources sont libérés si une exception se produit. Cela peut être fait dans le catch ou finally blocs.

Conclusion :

Ecrire un code sécurisé en Java est très difficile et il n'y a pas de formule magique qui permettra de résoudre vos problèmes de sécurité; tout ce que vous pouvez faire est de réfléchir (peut-être avec l'aide des outils d’analyse formelle) et d'utiliser des pratiques d'ingénierie prudentes pour minimiser les risques. Les conseils énoncées ici sont destinés à décrire certaines pratiques d'ingénierie prudentes pour écrire du code Java sécurisé. Ils ne vont pas résoudre vos problèmes de sécurité, mais ils permettront de réduire le nombre de façons dont les choses peuvent mal se passer. De plus certaine ne  ne seront  surement pas compatible avec votre conception classes.

La prochaine étape de cet article c'est comprendre la notion de bytecode et la sécurité lié au bytecode

8 commentaires:

  1. Je trouve cet article très intéressant pour la sécurité des codes java même si j'ai pas tout compris.
    J'ai pris conscience de beaucoup de danger que j'ignorais, et surtout beaucoup d'astuces pour un code mieux sécurisé.

    RépondreSupprimer
  2. Qu'est ce que tu n'a pas compris par exemple? Ca m'interesse de comprendre la problématique et d'essayer de t'apporter une reponse claire....

    RépondreSupprimer
    Réponses
    1. Dans la section "Rendez tous vos object immutable sauf nécéssité", vous avez dit de rendre le constructeur privé et construire des instances dans les Factory.

      Que signifie Factory?
      Comment peut on faire des instances dans les Factory?

      Supprimer
  3. Ce commentaire a été supprimé par l'auteur.

    RépondreSupprimer
  4. En fait un Factory est un pattern qui permet de construire une instance d’une classe parmi plusieurs disponibles en fonction d’une information donnée.
    Par exemple si tu veux construire une Maison tu peux faire une classe Factory qui aura des methodes suivantes :
    getBanco();getTole();getEau();getBrique().

    Il sont très utils pour ordonner l'instanciation des objects: C'est à dire centraliser la creation des objects..

    RépondreSupprimer
  5. Salam,merci pour ce tutorial tré riche.mais tu na pas parlé de la sécurité de serveur.si jai un application clien android et un server,qui pass via http comment je veux securisé mes donné du server? et merci

    RépondreSupprimer
  6. Sami, merci de votre interet sur ce blog mais en fait, cet article ne concerne pas la politique de securité d'un systeme (serveur ou autre) mais plutot la securité au niveau de code.
    Ta question sur la securité au niveau server demande d'autre competence dont j'ai pas la maitrise.

    RépondreSupprimer