undefined cover
undefined cover
#12 : Playtime cover
#12 : Playtime cover
La cybersécurité expliquée à ma grand-mère

#12 : Playtime

#12 : Playtime

18min |09/10/2023
Play
undefined cover
undefined cover
#12 : Playtime cover
#12 : Playtime cover
La cybersécurité expliquée à ma grand-mère

#12 : Playtime

#12 : Playtime

18min |09/10/2023
Play

Description

Pour comprendre ce qu'est un "buffer overflow" et une injection "SQL injection"

"Smashing the stack" (version original) https://inst.eecs.berkeley.edu/~cs161/fa08/papers/stack_smashing.pdf

(Traduction en français) https://www.arsouyes.org/phrack-trad/phrack49/phrack49_0x0e_SlasH.txt 


Les contres-mesures : https://www.arsouyes.org/blog/2019/57_Smashing_the_Stack_2020/index.fr.html


Hébergé par Ausha. Visitez ausha.co/politique-de-confidentialite pour plus d'informations.

Transcription

  • Speaker #0

    Bonjour mamie. Bonjour et bienvenue dans la cybersécurité expliquée à ma grand-mère, le podcast pour expliquer la cybersécurité à des gens qui n'y comprennent rien. Il est fréquemment question de vulnérabilité et surtout de leurs conséquences. Mais que définissons-nous précisément en évoquant les vulnérabilités ? De quels sujets traitons-nous exactement ? Existe-t-il des catégories ou des groupes de vulnérabilité et par conséquent des solutions universelles à appliquer ? Dans cet épisode, nous allons aborder deux familles de vulnérabilité qui, d'une certaine manière, illustrent toutes les autres catégories. Bien que ces groupes de vulnérabilité soient des groupes de vulnérabilité, paraissent être intrinsèquement distincts, ils demeurent néanmoins très proches les uns des autres. Cela s'apparente quelque peu à un univers fonctionnant sur différents niveaux, disposant d'un premier et d'un second plan, ainsi que de personnages qui ne font que des apparitions furtives dans cet univers. Il existe un film qui matérialise avec éloquence ce type de concept. Il s'agit de Playtime, réalisé par Jacques Tati en 1967. Ce film se révèle être complexe sur bien des aspects. En effet, Il semble dépeindre des scènes de la vie quotidienne, mais si l'on prête une attention méticuleuse, vous pourrez constater que, dans nombreuses scènes, l'action importante se déroule au second plan. Le personnage central, M. Law, sur lequel je reviendrai à la fin de cet épisode, suscite également un intérêt particulier, car dans certaines scènes, il altère le cours de l'histoire, parfois simplement par distraction. Un univers méthodiquement ordonné, des activités de premier et de second plan, Un personnage qui interagit dans cet univers et qui, par erreur ou maladresse, modifie le cours des choses, le décor est à présent établi pour aborder l'exploitation des failles de sécurité. Commençons par la famille de vulnérabilité la plus connue de toutes, le dépassement de tampons ou buffer overflow en anglais. Cette famille de vulnérabilité est d'autant plus populaire qu'elle a donné lieu à un papier très connu dans le monde de la cyber, le fameux Smatching the stack for fun and profit qui pourrait se traduire par Fracasser la pile, il faut bien dire que ça sent un peu à la scène de ménage. Cet article écrit par LF1 est sorti dans le magazine Frac dans le milieu des années 90. Comme d'habitude, vous trouverez les références dans la description de cet épisode. Ce texte explique très précisément comment fonctionne cette vulnérabilité. Avant de plonger dans les détails techniques, il convient dans un premier temps de comprendre l'univers dans lequel nous allons évoluer. Nous discutons ici du niveau le plus bas. Dans la mesure où les vulnérabilités attaquent directement la manière dont le processeur traite les données. La première chose à appréhender est que les données n'ont que la signification que l'on leur donne. Pour vous donner un exemple plus explicite, si je vous donne la valeur 10, votre esprit va très certainement immédiatement associer cette valeur avec le nombre entier 10. Or, cela pourrait représenter également le dixième caractère de l'alphabet, ou peut-être un code dont vous ignorez la signification. Il en va de même dans le domaine de l'informatique. Si vous détenez une donnée sans comprendre son interprétation, vous rencontrerez des difficultés à la traiter. Nous verrons dans quelques instants comment cette ambiguïté peut se révéler fort utile dans le contexte d'une attaque. Le second point qu'il est impératif de comprendre concerne le fonctionnement du processeur. Le rôle fondamental d'un processeur est d'exécuter des instructions et d'appliquer des actions sur des données. Un processeur requiert des instructions pour fonctionner. Pour exécuter ces instructions, un processeur dispose de registres. qui sont des petites cases mémoire directement accessibles à l'intérieur de celui-ci, et surtout directement utilisables par les instructions. S'il doit effectuer une addition, par exemple, il va charger une valeur dans le registre A, la seconde valeur dans le registre B, puis additionner la valeur du registre A au registre B. Au terme de l'opération, le résultat du calcul se trouvera dans le registre A. Généralement, suite à cette opération, il faudra stocker ce résultat à l'extérieur du processeur, c'est-à-dire dans la mémoire vive, notre fameuse RAM. Il faut voir la mémoire d'un ordinateur comme des millions de petites cases dans lesquelles vous pouvez placer des données. Chacune de ces cases est atteignable par son adresse. C'est exactement comme si vous aviez une très très grande rue composée de millions de maisons portant tout un numéro. Et bien ce numéro, c'est l'adresse en mémoire qui vous permet de stocker ou d'aller lire une donnée. Car rappelons-le, le processeur ne dispose que de quelques registres internes qu'il utilise pour exécuter ses instructions. Ces registres n'ont pas pour vocation à contenir des données sur le long terme. C'est pour cette raison que les processeurs interagissent en permanence avec la mémoire de l'ordinateur pour récupérer des données à traiter et stocker les résultats. On parle ici des données, mais il en va de même pour les instructions. Et c'est bien là qu'il faut commencer à bien comprendre le sens des données que le processeur devra traiter. Quand un programme s'exécute, la mémoire utilisée par ce programme est divisée en trois parties. La zone texte, la zone des données et la pile d'exécution. Généralement, la zone dite texte est la zone qui contient le programme lui-même. En pratique, cette zone ne varie pas car votre programme reste toujours le même. Ce sont les données qui changent et non le programme. Il y a ensuite la zone dite données En fait, cette zone est réservée à l'allocation de mémoire dont a besoin le programme pour fonctionner. Cette zone est beaucoup plus dynamique car le programme peut allouer de la mémoire en fonction de ses besoins. Il faut aussi souligner que si l'espace mémoire est alloué par le programme, c'est-à-dire réservé pour un usage spécifique, celui-ci doit aussi désallouer l'espace, c'est-à-dire le libérer. S'il ne fait pas, le programme va progressivement grignoter toutes les mémoires et va finir par cracher par manque de ressources. C'est ce qu'on appelle une fuite mémoire. Il reste encore une dernière zone, la pile mémoire. Il faut comprendre ici le mot pile comme une pile d'assiette.

  • Speaker #1

    Oh le blanc,

  • Speaker #0

    c'est le rythme qui compte.

  • Speaker #1

    Regarde un peu. Je prends l'assiette, je la...

  • Speaker #0

    Et pour comprendre son utilité, il faut comprendre comment un programme fonctionne et surtout comment un processeur agit dans ce cas-là. Comme il serait peu pratique de faire un programme directement dans le langage natif du processeur, on parle de programmeur en assembleur dans ce cas-là, on passe généralement par un langage intermédiaire de plus haut niveau. Ces langages sont généralement plus faciles à manipuler par un être humain. Mais pour que ce programme qui est compréhensible par un être humain soit exécutable par un processeur, il faut passer par une étape de traduction, qu'on appelle la compilation, et qui transforme le code source en instruction, directement exécutable par le processeur. Dans la très grande majorité des cas, vous allez voir que votre code source fait appel à des fonctions. C'est un peu comme en maths, quand vous donnez une valeur à une fonction et qu'elle vous renvoie le résultat. On peut faire appel à la fonction racine carré par exemple. Vous appelez la fonction racine carré en lui donnant un paramètre à un chiffre dont vous souhaitez avoir la racine carré. 9 par exemple. Vous appelez la fonction, elle vous retournera le résultat, c'est-à-dire 3. Cette action semble d'une simplicité déconcertante. Mais comment on va faire le processeur pour l'exécuter ? Il faut vous souvenir que le processeur est un peu Alzheimer, dans le sens où il a une mémoire à très court terme, c'est-à-dire les registres, et qu'en plus, il ne sait faire qu'une chose à la fois. C'est là où la pile prend tout son intérêt, car elle fonctionne comme un moyen de mémoriser des informations. Par exemple, le processeur peut empiler la valeur A dans la pile, et ensuite la valeur B. Quand il va dépiler une donnée, il va toujours récupérer la dernière valeur empilée, comme sur une pile d'assiettes. Dans notre exemple, il va récupérer la valeur B et ensuite la valeur A. Ce mécanisme est très simple et permet au processeur de stocker des données au-delà des quelques registres qu'il a en interne. Le processeur possède donc des registres dédiés à la gestion et l'utilisation de cette pile. C'est un peu technique, mais il faut simplement comprendre que le processeur a besoin, par exemple, de savoir où se situe la pile en mémoire. Dans le cas d'un appel à une fonction, le processeur va utiliser cette pile pour stocker l'adresse à laquelle il doit revenir à la fin de l'exécution de cette fonction. Il est un peu comme le petit poussé qui a besoin de retrouver son chemin. Dans le cas où la fonction utilise une variable locale, le compilateur va calculer l'emplacement de cette zone mémoire. En général, cette zone mémoire est localisée très proche de la zone mémoire utilisée par la pile. Il y a aussi une subtilité dans l'organisation de cette zone, car à chaque fois que vous empilez des données dans la pile, l'adresse de cette donnée va descendre. En fait, la pile a la tête à l'envers. Elle commence avec une adresse haute et plus vous empilez des données, et plus les données vont se retrouver plus bas. La zone mémoire utilisée par la variable locale, quant à elle, se retrouvera en dessous de la zone utilisée par la pile. Mais vous voyez intuitivement qu'il y a un problème quelque part. La pile descend alors que la zone utilisée par la variable, elle, est placée juste en dessous. Bien en principe, tout se passe bien car le compilateur a prévu le nombre exact de cases qu'il convient pour votre variable. Par exemple, 4 cases pour la variable locale, Et ensuite la zone utilisée pour la pile. La pile et la variable sont bien dans deux zones totalement distinctes, donc pas de problème. C'est vrai, si et seulement si vous mettez des données qui n'occuperont que 4 cases. Mais que se passe-t-il si vous ajoutez des données en plus ? Eh bien invariablement, vous allez empiéter sur la zone mémoire de la pile utilisée par le processeur. qui entre autres contient l'adresse à laquelle il doit revenir une fois la fonction exécutée. Bien c'est exactement ça un dépassement de tampon. Mais comment exploiter cette faiblesse ? Dans la plupart des cas le programme va tout simplement planter car la pile aura été altérée et l'adresse de retour n'aura aucun sens. Le processeur va s'y rendre et va trouver des données qui ne correspondent à aucune instruction valide. Concrètement si vous envoyez la chaîne de caractères A, B, C, D, E, F, G, H et que le système vous dit qu'il n'arrive pas à obtenir la solution, pas exécuter les instructions qui se trouvent à l'adresse hgfe avec une petite subtilité qui est que dans ce cas les données ne sont pas interprétées comme des lettres mais comme la valeur de l'adresse de retour et bien dans ce cas c'est que vous êtes proche du but noter que l'adresse doit souvent être inversée car la pile rappelons le est orienté vers le bas mais si maintenant vous remplacer les données qui sont aux emplacements occupés par les lettres e f gh par l'adresse d'une autre fonction du programme vous allez détourner l'exécution du processeur sur cette adresse. Car c'est cette valeur que le processeur va récupérer dans la pile et va interpréter comme étant l'adresse à laquelle il doit se rendre. Mais il y a encore mieux, vous pouvez même directement embarquer dans la chaîne de caractère, qu'on appelle un payload dans le jargon, des instructions à faire exécuter par le processeur directement. Cet exemple de vulnérabilité est extrêmement répandu et bien évidemment il existe plusieurs contre-mesures possibles, comme par exemple des manières différentes d'utiliser la mémoire mais aussi des options de compilation spécifiques. Mais la contre-mesure principale, celle qui est la plus importante, est de toujours contrôler les données qui viennent de l'extérieur. Je vais prendre un second exemple de faille de sécurité qui a priori n'a rien à voir avec l'exemple que l'on vient de voir, mais qui au final va se révéler être fortement similaire. Imaginez une page web qui doit vous authentifier en utilisant un nom d'utilisateur et votre mot de passe. Et pour ce faire, le serveur web va faire un appel à une base de données. Une base de données est un système qui permet de structurer des données dans des tables et permet de les interroger avec un langage spécifique. Dans notre exemple, on pourrait imaginer une table contenant deux colonnes, une première colonne contenant le nom de l'utilisateur et une seconde contenant le mot de passe. La requête qui sera utilisée pour vérifier votre identité sera de la forme suivante. Sélectionnez toutes les lignes où la colonne nom d'utilisateur est égale à la valeur fournie dans le champ nom utilisateur de la page web à la valeur du champ Mot de passe fourni par la page web. Si cette requête retourne un enregistrement, c'est que votre nom et votre mot de passe sont corrects. Si cette requête ne donne aucune réponse, c'est que vous n'êtes pas reconnu par le site web. Jusqu'ici, rien de bien compliqué. A noter que dans cette requête, on reprend directement les informations données par l'utilisateur dans les deux champs de la page d'accueil. L'authentification se base essentiellement sur une condition logique, ou plutôt sur deux conditions logiques. Il doit y avoir un enregistrement, qu'on appelle un tuple, dans la table des utilisateurs, où le champ Non utilisateur et Mot de passe correspondent aux valeurs données sur la page web. Si l'une des deux conditions est fausse, alors le résultat sera négatif. Mais dans notre cas, le système va simplement faire un copier-coller des valeurs fournies dans la page web. Ce qui veut dire concrètement que l'on peut changer la nature de la requête. Par exemple, dans le champ Non utilisateur ajoutez la clause Ou suivie du mot-clé. vrai. La requête qui sera envoyée à la base de données sera donc sélectionner toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur ou vrai et la colonne mot de passe égale à la valeur fournie par l'utilisateur dans la page sur le champ mot de passe. Ça semble très tordu et la vraie question est de savoir comment va réagir la base de données dans ce cas là. Et bien la première clause, celle qui correspond au nom utilisateur sera toujours vrai dans ce cas. Mais pourquoi ? Pour comprendre, il faut regarder attentivement cette première clause. Sélectionnez toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur ou vrai En fait, la présence de la clause ou suivie de vrai fait que systématiquement cette clause sera vraie. Car dans la logique boolean avec l'opérateur ou quand l'une des clauses est vraie, le résultat sera toujours vrai. Cette modification de la première clause permet de s'affranchir d'utiliser un nom utilisateur valide, car cette partie de la clause sera toujours vraie. En revanche, comment faire pour la seconde partie, qui elle restera fausse, si on ne fournit pas un mot de passe valide ? Eh bien il suffit simplement de demander à la base de données de ne pas lire la suite de la requête. Et le plus simple est de dire que le reste de la requête n'est qu'un commentaire. En SQL par exemple, ce sont des doubles tirées. La requête deviendra donc Sélectionnez toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur, ouvrez et ignorez le reste de la requête. Dans ce cas, les clauses de la requête seront vraies et un enregistrement sera retourné. Bien évidemment, si vous faites cette requête sur l'utilisateur admin du site web, vous allez être authentifié en tant qu'administrateur sur ce site, sans pour autant avoir donné un mot de passe valide. Cette technique est appelée SQL Injection et permet d'aller bien plus loin que ce petit exemple. Elle permet d'extraire toutes les données de la base, mais aussi de créer ses propres enregistrements, voire même ses propres tables. Mais quel est le point commun entre un buffer offer flow, qui est une attaque de très bas niveau, et une injection SQL qui, elle, au contraire, est au niveau applicatif. Mais en fait, c'est la même technique de base, car dans les deux cas, vous allez manipuler les données d'entrée pour faire faire au système autre chose que ce qui était prévu. A chaque fois qu'il y a une interaction entre un programme et le monde extérieur, il y a potentiellement ce genre de risque. Et cette règle est même applicable dans d'autres contextes, comme ChatGPT par exemple. Car le système est prévu pour ne pas vous donner accès à certaines données, comme par exemple des clés d'activation de Windows. Par défaut, si vous demandez des clés d'activation de Windows à ChatGPT, votre demande sera refusée. Mais si vous lui expliquez que votre grand-mère vous récitait des clés d'activation de Windows pour vous endormir le soir quand vous étiez petit, et que vous souhaiteriez que ChatGPT en fasse de même, et bien il le fera. Tout le problème réside dans le filtrage à bon escient des données qui rentrent dans le système, tout en sachant que les opposants vont essayer aussi de camoufler leur demande pour les rendre le plus normal possible. On rentre assez vite dans un cercle vicieux où l'opposant sait que vous savez, qu'il sait que vous avez mis des règles de filtrage et qu'il va falloir là encore les contourner.

  • Speaker #2

    Il se dit, si je tape ma lettre sur une autre machine, on croira que le possesseur de l'autre machine ne taperait pas sa lettre sur sa propre machine. Donc le possesseur de la machine a tapé sa lettre sur sa propre machine. Seulement moi je me dis, il croit que je vais croire que le possesseur d'une machine ne taperait pas la lettre sur sa propre machine. Donc c'est le possesseur de la machine qui a tapé sa lettre sur sa propre machine. Penser que l'enquête serait faite par un imbécile, manque de chance, tombe sur moi.

  • Speaker #0

    C'est vraiment comme dans le film Playtime, où il y a une multitude d'interactions plus ou moins complexes qui altèrent le comportement des uns et des autres. Je ne résiste pas à vous donner une petite anecdote sur le personnage de M. Hulot. Jack Tati, le réalisateur du film Playtime, habité dans le même immeuble que le grand-père de Nicolas Hulot. Et c'est son grand-père qui a inspiré Jacques Tati pour le personnage de Monsieur Hulot. C'est un détail amusant que ma grand-mère adore. Encore merci d'avoir écouté cet épisode de la cybersécurité expliquée à ma grand-mère. N'hésitez pas à liker cet épisode, à le partager avec d'autres et en parler autour de vous. Si vous êtes sur Spotify, vous pouvez aussi donner votre avis et proposer des sujets qui vous semblent pertinents. Mais surtout n'oubliez pas, pour certains la cybersécurité est un enjeu de vie ou de mort.

  • Speaker #3

    Il n'y a plus sérieux que ça.

Description

Pour comprendre ce qu'est un "buffer overflow" et une injection "SQL injection"

"Smashing the stack" (version original) https://inst.eecs.berkeley.edu/~cs161/fa08/papers/stack_smashing.pdf

(Traduction en français) https://www.arsouyes.org/phrack-trad/phrack49/phrack49_0x0e_SlasH.txt 


Les contres-mesures : https://www.arsouyes.org/blog/2019/57_Smashing_the_Stack_2020/index.fr.html


Hébergé par Ausha. Visitez ausha.co/politique-de-confidentialite pour plus d'informations.

Transcription

  • Speaker #0

    Bonjour mamie. Bonjour et bienvenue dans la cybersécurité expliquée à ma grand-mère, le podcast pour expliquer la cybersécurité à des gens qui n'y comprennent rien. Il est fréquemment question de vulnérabilité et surtout de leurs conséquences. Mais que définissons-nous précisément en évoquant les vulnérabilités ? De quels sujets traitons-nous exactement ? Existe-t-il des catégories ou des groupes de vulnérabilité et par conséquent des solutions universelles à appliquer ? Dans cet épisode, nous allons aborder deux familles de vulnérabilité qui, d'une certaine manière, illustrent toutes les autres catégories. Bien que ces groupes de vulnérabilité soient des groupes de vulnérabilité, paraissent être intrinsèquement distincts, ils demeurent néanmoins très proches les uns des autres. Cela s'apparente quelque peu à un univers fonctionnant sur différents niveaux, disposant d'un premier et d'un second plan, ainsi que de personnages qui ne font que des apparitions furtives dans cet univers. Il existe un film qui matérialise avec éloquence ce type de concept. Il s'agit de Playtime, réalisé par Jacques Tati en 1967. Ce film se révèle être complexe sur bien des aspects. En effet, Il semble dépeindre des scènes de la vie quotidienne, mais si l'on prête une attention méticuleuse, vous pourrez constater que, dans nombreuses scènes, l'action importante se déroule au second plan. Le personnage central, M. Law, sur lequel je reviendrai à la fin de cet épisode, suscite également un intérêt particulier, car dans certaines scènes, il altère le cours de l'histoire, parfois simplement par distraction. Un univers méthodiquement ordonné, des activités de premier et de second plan, Un personnage qui interagit dans cet univers et qui, par erreur ou maladresse, modifie le cours des choses, le décor est à présent établi pour aborder l'exploitation des failles de sécurité. Commençons par la famille de vulnérabilité la plus connue de toutes, le dépassement de tampons ou buffer overflow en anglais. Cette famille de vulnérabilité est d'autant plus populaire qu'elle a donné lieu à un papier très connu dans le monde de la cyber, le fameux Smatching the stack for fun and profit qui pourrait se traduire par Fracasser la pile, il faut bien dire que ça sent un peu à la scène de ménage. Cet article écrit par LF1 est sorti dans le magazine Frac dans le milieu des années 90. Comme d'habitude, vous trouverez les références dans la description de cet épisode. Ce texte explique très précisément comment fonctionne cette vulnérabilité. Avant de plonger dans les détails techniques, il convient dans un premier temps de comprendre l'univers dans lequel nous allons évoluer. Nous discutons ici du niveau le plus bas. Dans la mesure où les vulnérabilités attaquent directement la manière dont le processeur traite les données. La première chose à appréhender est que les données n'ont que la signification que l'on leur donne. Pour vous donner un exemple plus explicite, si je vous donne la valeur 10, votre esprit va très certainement immédiatement associer cette valeur avec le nombre entier 10. Or, cela pourrait représenter également le dixième caractère de l'alphabet, ou peut-être un code dont vous ignorez la signification. Il en va de même dans le domaine de l'informatique. Si vous détenez une donnée sans comprendre son interprétation, vous rencontrerez des difficultés à la traiter. Nous verrons dans quelques instants comment cette ambiguïté peut se révéler fort utile dans le contexte d'une attaque. Le second point qu'il est impératif de comprendre concerne le fonctionnement du processeur. Le rôle fondamental d'un processeur est d'exécuter des instructions et d'appliquer des actions sur des données. Un processeur requiert des instructions pour fonctionner. Pour exécuter ces instructions, un processeur dispose de registres. qui sont des petites cases mémoire directement accessibles à l'intérieur de celui-ci, et surtout directement utilisables par les instructions. S'il doit effectuer une addition, par exemple, il va charger une valeur dans le registre A, la seconde valeur dans le registre B, puis additionner la valeur du registre A au registre B. Au terme de l'opération, le résultat du calcul se trouvera dans le registre A. Généralement, suite à cette opération, il faudra stocker ce résultat à l'extérieur du processeur, c'est-à-dire dans la mémoire vive, notre fameuse RAM. Il faut voir la mémoire d'un ordinateur comme des millions de petites cases dans lesquelles vous pouvez placer des données. Chacune de ces cases est atteignable par son adresse. C'est exactement comme si vous aviez une très très grande rue composée de millions de maisons portant tout un numéro. Et bien ce numéro, c'est l'adresse en mémoire qui vous permet de stocker ou d'aller lire une donnée. Car rappelons-le, le processeur ne dispose que de quelques registres internes qu'il utilise pour exécuter ses instructions. Ces registres n'ont pas pour vocation à contenir des données sur le long terme. C'est pour cette raison que les processeurs interagissent en permanence avec la mémoire de l'ordinateur pour récupérer des données à traiter et stocker les résultats. On parle ici des données, mais il en va de même pour les instructions. Et c'est bien là qu'il faut commencer à bien comprendre le sens des données que le processeur devra traiter. Quand un programme s'exécute, la mémoire utilisée par ce programme est divisée en trois parties. La zone texte, la zone des données et la pile d'exécution. Généralement, la zone dite texte est la zone qui contient le programme lui-même. En pratique, cette zone ne varie pas car votre programme reste toujours le même. Ce sont les données qui changent et non le programme. Il y a ensuite la zone dite données En fait, cette zone est réservée à l'allocation de mémoire dont a besoin le programme pour fonctionner. Cette zone est beaucoup plus dynamique car le programme peut allouer de la mémoire en fonction de ses besoins. Il faut aussi souligner que si l'espace mémoire est alloué par le programme, c'est-à-dire réservé pour un usage spécifique, celui-ci doit aussi désallouer l'espace, c'est-à-dire le libérer. S'il ne fait pas, le programme va progressivement grignoter toutes les mémoires et va finir par cracher par manque de ressources. C'est ce qu'on appelle une fuite mémoire. Il reste encore une dernière zone, la pile mémoire. Il faut comprendre ici le mot pile comme une pile d'assiette.

  • Speaker #1

    Oh le blanc,

  • Speaker #0

    c'est le rythme qui compte.

  • Speaker #1

    Regarde un peu. Je prends l'assiette, je la...

  • Speaker #0

    Et pour comprendre son utilité, il faut comprendre comment un programme fonctionne et surtout comment un processeur agit dans ce cas-là. Comme il serait peu pratique de faire un programme directement dans le langage natif du processeur, on parle de programmeur en assembleur dans ce cas-là, on passe généralement par un langage intermédiaire de plus haut niveau. Ces langages sont généralement plus faciles à manipuler par un être humain. Mais pour que ce programme qui est compréhensible par un être humain soit exécutable par un processeur, il faut passer par une étape de traduction, qu'on appelle la compilation, et qui transforme le code source en instruction, directement exécutable par le processeur. Dans la très grande majorité des cas, vous allez voir que votre code source fait appel à des fonctions. C'est un peu comme en maths, quand vous donnez une valeur à une fonction et qu'elle vous renvoie le résultat. On peut faire appel à la fonction racine carré par exemple. Vous appelez la fonction racine carré en lui donnant un paramètre à un chiffre dont vous souhaitez avoir la racine carré. 9 par exemple. Vous appelez la fonction, elle vous retournera le résultat, c'est-à-dire 3. Cette action semble d'une simplicité déconcertante. Mais comment on va faire le processeur pour l'exécuter ? Il faut vous souvenir que le processeur est un peu Alzheimer, dans le sens où il a une mémoire à très court terme, c'est-à-dire les registres, et qu'en plus, il ne sait faire qu'une chose à la fois. C'est là où la pile prend tout son intérêt, car elle fonctionne comme un moyen de mémoriser des informations. Par exemple, le processeur peut empiler la valeur A dans la pile, et ensuite la valeur B. Quand il va dépiler une donnée, il va toujours récupérer la dernière valeur empilée, comme sur une pile d'assiettes. Dans notre exemple, il va récupérer la valeur B et ensuite la valeur A. Ce mécanisme est très simple et permet au processeur de stocker des données au-delà des quelques registres qu'il a en interne. Le processeur possède donc des registres dédiés à la gestion et l'utilisation de cette pile. C'est un peu technique, mais il faut simplement comprendre que le processeur a besoin, par exemple, de savoir où se situe la pile en mémoire. Dans le cas d'un appel à une fonction, le processeur va utiliser cette pile pour stocker l'adresse à laquelle il doit revenir à la fin de l'exécution de cette fonction. Il est un peu comme le petit poussé qui a besoin de retrouver son chemin. Dans le cas où la fonction utilise une variable locale, le compilateur va calculer l'emplacement de cette zone mémoire. En général, cette zone mémoire est localisée très proche de la zone mémoire utilisée par la pile. Il y a aussi une subtilité dans l'organisation de cette zone, car à chaque fois que vous empilez des données dans la pile, l'adresse de cette donnée va descendre. En fait, la pile a la tête à l'envers. Elle commence avec une adresse haute et plus vous empilez des données, et plus les données vont se retrouver plus bas. La zone mémoire utilisée par la variable locale, quant à elle, se retrouvera en dessous de la zone utilisée par la pile. Mais vous voyez intuitivement qu'il y a un problème quelque part. La pile descend alors que la zone utilisée par la variable, elle, est placée juste en dessous. Bien en principe, tout se passe bien car le compilateur a prévu le nombre exact de cases qu'il convient pour votre variable. Par exemple, 4 cases pour la variable locale, Et ensuite la zone utilisée pour la pile. La pile et la variable sont bien dans deux zones totalement distinctes, donc pas de problème. C'est vrai, si et seulement si vous mettez des données qui n'occuperont que 4 cases. Mais que se passe-t-il si vous ajoutez des données en plus ? Eh bien invariablement, vous allez empiéter sur la zone mémoire de la pile utilisée par le processeur. qui entre autres contient l'adresse à laquelle il doit revenir une fois la fonction exécutée. Bien c'est exactement ça un dépassement de tampon. Mais comment exploiter cette faiblesse ? Dans la plupart des cas le programme va tout simplement planter car la pile aura été altérée et l'adresse de retour n'aura aucun sens. Le processeur va s'y rendre et va trouver des données qui ne correspondent à aucune instruction valide. Concrètement si vous envoyez la chaîne de caractères A, B, C, D, E, F, G, H et que le système vous dit qu'il n'arrive pas à obtenir la solution, pas exécuter les instructions qui se trouvent à l'adresse hgfe avec une petite subtilité qui est que dans ce cas les données ne sont pas interprétées comme des lettres mais comme la valeur de l'adresse de retour et bien dans ce cas c'est que vous êtes proche du but noter que l'adresse doit souvent être inversée car la pile rappelons le est orienté vers le bas mais si maintenant vous remplacer les données qui sont aux emplacements occupés par les lettres e f gh par l'adresse d'une autre fonction du programme vous allez détourner l'exécution du processeur sur cette adresse. Car c'est cette valeur que le processeur va récupérer dans la pile et va interpréter comme étant l'adresse à laquelle il doit se rendre. Mais il y a encore mieux, vous pouvez même directement embarquer dans la chaîne de caractère, qu'on appelle un payload dans le jargon, des instructions à faire exécuter par le processeur directement. Cet exemple de vulnérabilité est extrêmement répandu et bien évidemment il existe plusieurs contre-mesures possibles, comme par exemple des manières différentes d'utiliser la mémoire mais aussi des options de compilation spécifiques. Mais la contre-mesure principale, celle qui est la plus importante, est de toujours contrôler les données qui viennent de l'extérieur. Je vais prendre un second exemple de faille de sécurité qui a priori n'a rien à voir avec l'exemple que l'on vient de voir, mais qui au final va se révéler être fortement similaire. Imaginez une page web qui doit vous authentifier en utilisant un nom d'utilisateur et votre mot de passe. Et pour ce faire, le serveur web va faire un appel à une base de données. Une base de données est un système qui permet de structurer des données dans des tables et permet de les interroger avec un langage spécifique. Dans notre exemple, on pourrait imaginer une table contenant deux colonnes, une première colonne contenant le nom de l'utilisateur et une seconde contenant le mot de passe. La requête qui sera utilisée pour vérifier votre identité sera de la forme suivante. Sélectionnez toutes les lignes où la colonne nom d'utilisateur est égale à la valeur fournie dans le champ nom utilisateur de la page web à la valeur du champ Mot de passe fourni par la page web. Si cette requête retourne un enregistrement, c'est que votre nom et votre mot de passe sont corrects. Si cette requête ne donne aucune réponse, c'est que vous n'êtes pas reconnu par le site web. Jusqu'ici, rien de bien compliqué. A noter que dans cette requête, on reprend directement les informations données par l'utilisateur dans les deux champs de la page d'accueil. L'authentification se base essentiellement sur une condition logique, ou plutôt sur deux conditions logiques. Il doit y avoir un enregistrement, qu'on appelle un tuple, dans la table des utilisateurs, où le champ Non utilisateur et Mot de passe correspondent aux valeurs données sur la page web. Si l'une des deux conditions est fausse, alors le résultat sera négatif. Mais dans notre cas, le système va simplement faire un copier-coller des valeurs fournies dans la page web. Ce qui veut dire concrètement que l'on peut changer la nature de la requête. Par exemple, dans le champ Non utilisateur ajoutez la clause Ou suivie du mot-clé. vrai. La requête qui sera envoyée à la base de données sera donc sélectionner toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur ou vrai et la colonne mot de passe égale à la valeur fournie par l'utilisateur dans la page sur le champ mot de passe. Ça semble très tordu et la vraie question est de savoir comment va réagir la base de données dans ce cas là. Et bien la première clause, celle qui correspond au nom utilisateur sera toujours vrai dans ce cas. Mais pourquoi ? Pour comprendre, il faut regarder attentivement cette première clause. Sélectionnez toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur ou vrai En fait, la présence de la clause ou suivie de vrai fait que systématiquement cette clause sera vraie. Car dans la logique boolean avec l'opérateur ou quand l'une des clauses est vraie, le résultat sera toujours vrai. Cette modification de la première clause permet de s'affranchir d'utiliser un nom utilisateur valide, car cette partie de la clause sera toujours vraie. En revanche, comment faire pour la seconde partie, qui elle restera fausse, si on ne fournit pas un mot de passe valide ? Eh bien il suffit simplement de demander à la base de données de ne pas lire la suite de la requête. Et le plus simple est de dire que le reste de la requête n'est qu'un commentaire. En SQL par exemple, ce sont des doubles tirées. La requête deviendra donc Sélectionnez toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur, ouvrez et ignorez le reste de la requête. Dans ce cas, les clauses de la requête seront vraies et un enregistrement sera retourné. Bien évidemment, si vous faites cette requête sur l'utilisateur admin du site web, vous allez être authentifié en tant qu'administrateur sur ce site, sans pour autant avoir donné un mot de passe valide. Cette technique est appelée SQL Injection et permet d'aller bien plus loin que ce petit exemple. Elle permet d'extraire toutes les données de la base, mais aussi de créer ses propres enregistrements, voire même ses propres tables. Mais quel est le point commun entre un buffer offer flow, qui est une attaque de très bas niveau, et une injection SQL qui, elle, au contraire, est au niveau applicatif. Mais en fait, c'est la même technique de base, car dans les deux cas, vous allez manipuler les données d'entrée pour faire faire au système autre chose que ce qui était prévu. A chaque fois qu'il y a une interaction entre un programme et le monde extérieur, il y a potentiellement ce genre de risque. Et cette règle est même applicable dans d'autres contextes, comme ChatGPT par exemple. Car le système est prévu pour ne pas vous donner accès à certaines données, comme par exemple des clés d'activation de Windows. Par défaut, si vous demandez des clés d'activation de Windows à ChatGPT, votre demande sera refusée. Mais si vous lui expliquez que votre grand-mère vous récitait des clés d'activation de Windows pour vous endormir le soir quand vous étiez petit, et que vous souhaiteriez que ChatGPT en fasse de même, et bien il le fera. Tout le problème réside dans le filtrage à bon escient des données qui rentrent dans le système, tout en sachant que les opposants vont essayer aussi de camoufler leur demande pour les rendre le plus normal possible. On rentre assez vite dans un cercle vicieux où l'opposant sait que vous savez, qu'il sait que vous avez mis des règles de filtrage et qu'il va falloir là encore les contourner.

  • Speaker #2

    Il se dit, si je tape ma lettre sur une autre machine, on croira que le possesseur de l'autre machine ne taperait pas sa lettre sur sa propre machine. Donc le possesseur de la machine a tapé sa lettre sur sa propre machine. Seulement moi je me dis, il croit que je vais croire que le possesseur d'une machine ne taperait pas la lettre sur sa propre machine. Donc c'est le possesseur de la machine qui a tapé sa lettre sur sa propre machine. Penser que l'enquête serait faite par un imbécile, manque de chance, tombe sur moi.

  • Speaker #0

    C'est vraiment comme dans le film Playtime, où il y a une multitude d'interactions plus ou moins complexes qui altèrent le comportement des uns et des autres. Je ne résiste pas à vous donner une petite anecdote sur le personnage de M. Hulot. Jack Tati, le réalisateur du film Playtime, habité dans le même immeuble que le grand-père de Nicolas Hulot. Et c'est son grand-père qui a inspiré Jacques Tati pour le personnage de Monsieur Hulot. C'est un détail amusant que ma grand-mère adore. Encore merci d'avoir écouté cet épisode de la cybersécurité expliquée à ma grand-mère. N'hésitez pas à liker cet épisode, à le partager avec d'autres et en parler autour de vous. Si vous êtes sur Spotify, vous pouvez aussi donner votre avis et proposer des sujets qui vous semblent pertinents. Mais surtout n'oubliez pas, pour certains la cybersécurité est un enjeu de vie ou de mort.

  • Speaker #3

    Il n'y a plus sérieux que ça.

Share

Embed

You may also like

Description

Pour comprendre ce qu'est un "buffer overflow" et une injection "SQL injection"

"Smashing the stack" (version original) https://inst.eecs.berkeley.edu/~cs161/fa08/papers/stack_smashing.pdf

(Traduction en français) https://www.arsouyes.org/phrack-trad/phrack49/phrack49_0x0e_SlasH.txt 


Les contres-mesures : https://www.arsouyes.org/blog/2019/57_Smashing_the_Stack_2020/index.fr.html


Hébergé par Ausha. Visitez ausha.co/politique-de-confidentialite pour plus d'informations.

Transcription

  • Speaker #0

    Bonjour mamie. Bonjour et bienvenue dans la cybersécurité expliquée à ma grand-mère, le podcast pour expliquer la cybersécurité à des gens qui n'y comprennent rien. Il est fréquemment question de vulnérabilité et surtout de leurs conséquences. Mais que définissons-nous précisément en évoquant les vulnérabilités ? De quels sujets traitons-nous exactement ? Existe-t-il des catégories ou des groupes de vulnérabilité et par conséquent des solutions universelles à appliquer ? Dans cet épisode, nous allons aborder deux familles de vulnérabilité qui, d'une certaine manière, illustrent toutes les autres catégories. Bien que ces groupes de vulnérabilité soient des groupes de vulnérabilité, paraissent être intrinsèquement distincts, ils demeurent néanmoins très proches les uns des autres. Cela s'apparente quelque peu à un univers fonctionnant sur différents niveaux, disposant d'un premier et d'un second plan, ainsi que de personnages qui ne font que des apparitions furtives dans cet univers. Il existe un film qui matérialise avec éloquence ce type de concept. Il s'agit de Playtime, réalisé par Jacques Tati en 1967. Ce film se révèle être complexe sur bien des aspects. En effet, Il semble dépeindre des scènes de la vie quotidienne, mais si l'on prête une attention méticuleuse, vous pourrez constater que, dans nombreuses scènes, l'action importante se déroule au second plan. Le personnage central, M. Law, sur lequel je reviendrai à la fin de cet épisode, suscite également un intérêt particulier, car dans certaines scènes, il altère le cours de l'histoire, parfois simplement par distraction. Un univers méthodiquement ordonné, des activités de premier et de second plan, Un personnage qui interagit dans cet univers et qui, par erreur ou maladresse, modifie le cours des choses, le décor est à présent établi pour aborder l'exploitation des failles de sécurité. Commençons par la famille de vulnérabilité la plus connue de toutes, le dépassement de tampons ou buffer overflow en anglais. Cette famille de vulnérabilité est d'autant plus populaire qu'elle a donné lieu à un papier très connu dans le monde de la cyber, le fameux Smatching the stack for fun and profit qui pourrait se traduire par Fracasser la pile, il faut bien dire que ça sent un peu à la scène de ménage. Cet article écrit par LF1 est sorti dans le magazine Frac dans le milieu des années 90. Comme d'habitude, vous trouverez les références dans la description de cet épisode. Ce texte explique très précisément comment fonctionne cette vulnérabilité. Avant de plonger dans les détails techniques, il convient dans un premier temps de comprendre l'univers dans lequel nous allons évoluer. Nous discutons ici du niveau le plus bas. Dans la mesure où les vulnérabilités attaquent directement la manière dont le processeur traite les données. La première chose à appréhender est que les données n'ont que la signification que l'on leur donne. Pour vous donner un exemple plus explicite, si je vous donne la valeur 10, votre esprit va très certainement immédiatement associer cette valeur avec le nombre entier 10. Or, cela pourrait représenter également le dixième caractère de l'alphabet, ou peut-être un code dont vous ignorez la signification. Il en va de même dans le domaine de l'informatique. Si vous détenez une donnée sans comprendre son interprétation, vous rencontrerez des difficultés à la traiter. Nous verrons dans quelques instants comment cette ambiguïté peut se révéler fort utile dans le contexte d'une attaque. Le second point qu'il est impératif de comprendre concerne le fonctionnement du processeur. Le rôle fondamental d'un processeur est d'exécuter des instructions et d'appliquer des actions sur des données. Un processeur requiert des instructions pour fonctionner. Pour exécuter ces instructions, un processeur dispose de registres. qui sont des petites cases mémoire directement accessibles à l'intérieur de celui-ci, et surtout directement utilisables par les instructions. S'il doit effectuer une addition, par exemple, il va charger une valeur dans le registre A, la seconde valeur dans le registre B, puis additionner la valeur du registre A au registre B. Au terme de l'opération, le résultat du calcul se trouvera dans le registre A. Généralement, suite à cette opération, il faudra stocker ce résultat à l'extérieur du processeur, c'est-à-dire dans la mémoire vive, notre fameuse RAM. Il faut voir la mémoire d'un ordinateur comme des millions de petites cases dans lesquelles vous pouvez placer des données. Chacune de ces cases est atteignable par son adresse. C'est exactement comme si vous aviez une très très grande rue composée de millions de maisons portant tout un numéro. Et bien ce numéro, c'est l'adresse en mémoire qui vous permet de stocker ou d'aller lire une donnée. Car rappelons-le, le processeur ne dispose que de quelques registres internes qu'il utilise pour exécuter ses instructions. Ces registres n'ont pas pour vocation à contenir des données sur le long terme. C'est pour cette raison que les processeurs interagissent en permanence avec la mémoire de l'ordinateur pour récupérer des données à traiter et stocker les résultats. On parle ici des données, mais il en va de même pour les instructions. Et c'est bien là qu'il faut commencer à bien comprendre le sens des données que le processeur devra traiter. Quand un programme s'exécute, la mémoire utilisée par ce programme est divisée en trois parties. La zone texte, la zone des données et la pile d'exécution. Généralement, la zone dite texte est la zone qui contient le programme lui-même. En pratique, cette zone ne varie pas car votre programme reste toujours le même. Ce sont les données qui changent et non le programme. Il y a ensuite la zone dite données En fait, cette zone est réservée à l'allocation de mémoire dont a besoin le programme pour fonctionner. Cette zone est beaucoup plus dynamique car le programme peut allouer de la mémoire en fonction de ses besoins. Il faut aussi souligner que si l'espace mémoire est alloué par le programme, c'est-à-dire réservé pour un usage spécifique, celui-ci doit aussi désallouer l'espace, c'est-à-dire le libérer. S'il ne fait pas, le programme va progressivement grignoter toutes les mémoires et va finir par cracher par manque de ressources. C'est ce qu'on appelle une fuite mémoire. Il reste encore une dernière zone, la pile mémoire. Il faut comprendre ici le mot pile comme une pile d'assiette.

  • Speaker #1

    Oh le blanc,

  • Speaker #0

    c'est le rythme qui compte.

  • Speaker #1

    Regarde un peu. Je prends l'assiette, je la...

  • Speaker #0

    Et pour comprendre son utilité, il faut comprendre comment un programme fonctionne et surtout comment un processeur agit dans ce cas-là. Comme il serait peu pratique de faire un programme directement dans le langage natif du processeur, on parle de programmeur en assembleur dans ce cas-là, on passe généralement par un langage intermédiaire de plus haut niveau. Ces langages sont généralement plus faciles à manipuler par un être humain. Mais pour que ce programme qui est compréhensible par un être humain soit exécutable par un processeur, il faut passer par une étape de traduction, qu'on appelle la compilation, et qui transforme le code source en instruction, directement exécutable par le processeur. Dans la très grande majorité des cas, vous allez voir que votre code source fait appel à des fonctions. C'est un peu comme en maths, quand vous donnez une valeur à une fonction et qu'elle vous renvoie le résultat. On peut faire appel à la fonction racine carré par exemple. Vous appelez la fonction racine carré en lui donnant un paramètre à un chiffre dont vous souhaitez avoir la racine carré. 9 par exemple. Vous appelez la fonction, elle vous retournera le résultat, c'est-à-dire 3. Cette action semble d'une simplicité déconcertante. Mais comment on va faire le processeur pour l'exécuter ? Il faut vous souvenir que le processeur est un peu Alzheimer, dans le sens où il a une mémoire à très court terme, c'est-à-dire les registres, et qu'en plus, il ne sait faire qu'une chose à la fois. C'est là où la pile prend tout son intérêt, car elle fonctionne comme un moyen de mémoriser des informations. Par exemple, le processeur peut empiler la valeur A dans la pile, et ensuite la valeur B. Quand il va dépiler une donnée, il va toujours récupérer la dernière valeur empilée, comme sur une pile d'assiettes. Dans notre exemple, il va récupérer la valeur B et ensuite la valeur A. Ce mécanisme est très simple et permet au processeur de stocker des données au-delà des quelques registres qu'il a en interne. Le processeur possède donc des registres dédiés à la gestion et l'utilisation de cette pile. C'est un peu technique, mais il faut simplement comprendre que le processeur a besoin, par exemple, de savoir où se situe la pile en mémoire. Dans le cas d'un appel à une fonction, le processeur va utiliser cette pile pour stocker l'adresse à laquelle il doit revenir à la fin de l'exécution de cette fonction. Il est un peu comme le petit poussé qui a besoin de retrouver son chemin. Dans le cas où la fonction utilise une variable locale, le compilateur va calculer l'emplacement de cette zone mémoire. En général, cette zone mémoire est localisée très proche de la zone mémoire utilisée par la pile. Il y a aussi une subtilité dans l'organisation de cette zone, car à chaque fois que vous empilez des données dans la pile, l'adresse de cette donnée va descendre. En fait, la pile a la tête à l'envers. Elle commence avec une adresse haute et plus vous empilez des données, et plus les données vont se retrouver plus bas. La zone mémoire utilisée par la variable locale, quant à elle, se retrouvera en dessous de la zone utilisée par la pile. Mais vous voyez intuitivement qu'il y a un problème quelque part. La pile descend alors que la zone utilisée par la variable, elle, est placée juste en dessous. Bien en principe, tout se passe bien car le compilateur a prévu le nombre exact de cases qu'il convient pour votre variable. Par exemple, 4 cases pour la variable locale, Et ensuite la zone utilisée pour la pile. La pile et la variable sont bien dans deux zones totalement distinctes, donc pas de problème. C'est vrai, si et seulement si vous mettez des données qui n'occuperont que 4 cases. Mais que se passe-t-il si vous ajoutez des données en plus ? Eh bien invariablement, vous allez empiéter sur la zone mémoire de la pile utilisée par le processeur. qui entre autres contient l'adresse à laquelle il doit revenir une fois la fonction exécutée. Bien c'est exactement ça un dépassement de tampon. Mais comment exploiter cette faiblesse ? Dans la plupart des cas le programme va tout simplement planter car la pile aura été altérée et l'adresse de retour n'aura aucun sens. Le processeur va s'y rendre et va trouver des données qui ne correspondent à aucune instruction valide. Concrètement si vous envoyez la chaîne de caractères A, B, C, D, E, F, G, H et que le système vous dit qu'il n'arrive pas à obtenir la solution, pas exécuter les instructions qui se trouvent à l'adresse hgfe avec une petite subtilité qui est que dans ce cas les données ne sont pas interprétées comme des lettres mais comme la valeur de l'adresse de retour et bien dans ce cas c'est que vous êtes proche du but noter que l'adresse doit souvent être inversée car la pile rappelons le est orienté vers le bas mais si maintenant vous remplacer les données qui sont aux emplacements occupés par les lettres e f gh par l'adresse d'une autre fonction du programme vous allez détourner l'exécution du processeur sur cette adresse. Car c'est cette valeur que le processeur va récupérer dans la pile et va interpréter comme étant l'adresse à laquelle il doit se rendre. Mais il y a encore mieux, vous pouvez même directement embarquer dans la chaîne de caractère, qu'on appelle un payload dans le jargon, des instructions à faire exécuter par le processeur directement. Cet exemple de vulnérabilité est extrêmement répandu et bien évidemment il existe plusieurs contre-mesures possibles, comme par exemple des manières différentes d'utiliser la mémoire mais aussi des options de compilation spécifiques. Mais la contre-mesure principale, celle qui est la plus importante, est de toujours contrôler les données qui viennent de l'extérieur. Je vais prendre un second exemple de faille de sécurité qui a priori n'a rien à voir avec l'exemple que l'on vient de voir, mais qui au final va se révéler être fortement similaire. Imaginez une page web qui doit vous authentifier en utilisant un nom d'utilisateur et votre mot de passe. Et pour ce faire, le serveur web va faire un appel à une base de données. Une base de données est un système qui permet de structurer des données dans des tables et permet de les interroger avec un langage spécifique. Dans notre exemple, on pourrait imaginer une table contenant deux colonnes, une première colonne contenant le nom de l'utilisateur et une seconde contenant le mot de passe. La requête qui sera utilisée pour vérifier votre identité sera de la forme suivante. Sélectionnez toutes les lignes où la colonne nom d'utilisateur est égale à la valeur fournie dans le champ nom utilisateur de la page web à la valeur du champ Mot de passe fourni par la page web. Si cette requête retourne un enregistrement, c'est que votre nom et votre mot de passe sont corrects. Si cette requête ne donne aucune réponse, c'est que vous n'êtes pas reconnu par le site web. Jusqu'ici, rien de bien compliqué. A noter que dans cette requête, on reprend directement les informations données par l'utilisateur dans les deux champs de la page d'accueil. L'authentification se base essentiellement sur une condition logique, ou plutôt sur deux conditions logiques. Il doit y avoir un enregistrement, qu'on appelle un tuple, dans la table des utilisateurs, où le champ Non utilisateur et Mot de passe correspondent aux valeurs données sur la page web. Si l'une des deux conditions est fausse, alors le résultat sera négatif. Mais dans notre cas, le système va simplement faire un copier-coller des valeurs fournies dans la page web. Ce qui veut dire concrètement que l'on peut changer la nature de la requête. Par exemple, dans le champ Non utilisateur ajoutez la clause Ou suivie du mot-clé. vrai. La requête qui sera envoyée à la base de données sera donc sélectionner toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur ou vrai et la colonne mot de passe égale à la valeur fournie par l'utilisateur dans la page sur le champ mot de passe. Ça semble très tordu et la vraie question est de savoir comment va réagir la base de données dans ce cas là. Et bien la première clause, celle qui correspond au nom utilisateur sera toujours vrai dans ce cas. Mais pourquoi ? Pour comprendre, il faut regarder attentivement cette première clause. Sélectionnez toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur ou vrai En fait, la présence de la clause ou suivie de vrai fait que systématiquement cette clause sera vraie. Car dans la logique boolean avec l'opérateur ou quand l'une des clauses est vraie, le résultat sera toujours vrai. Cette modification de la première clause permet de s'affranchir d'utiliser un nom utilisateur valide, car cette partie de la clause sera toujours vraie. En revanche, comment faire pour la seconde partie, qui elle restera fausse, si on ne fournit pas un mot de passe valide ? Eh bien il suffit simplement de demander à la base de données de ne pas lire la suite de la requête. Et le plus simple est de dire que le reste de la requête n'est qu'un commentaire. En SQL par exemple, ce sont des doubles tirées. La requête deviendra donc Sélectionnez toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur, ouvrez et ignorez le reste de la requête. Dans ce cas, les clauses de la requête seront vraies et un enregistrement sera retourné. Bien évidemment, si vous faites cette requête sur l'utilisateur admin du site web, vous allez être authentifié en tant qu'administrateur sur ce site, sans pour autant avoir donné un mot de passe valide. Cette technique est appelée SQL Injection et permet d'aller bien plus loin que ce petit exemple. Elle permet d'extraire toutes les données de la base, mais aussi de créer ses propres enregistrements, voire même ses propres tables. Mais quel est le point commun entre un buffer offer flow, qui est une attaque de très bas niveau, et une injection SQL qui, elle, au contraire, est au niveau applicatif. Mais en fait, c'est la même technique de base, car dans les deux cas, vous allez manipuler les données d'entrée pour faire faire au système autre chose que ce qui était prévu. A chaque fois qu'il y a une interaction entre un programme et le monde extérieur, il y a potentiellement ce genre de risque. Et cette règle est même applicable dans d'autres contextes, comme ChatGPT par exemple. Car le système est prévu pour ne pas vous donner accès à certaines données, comme par exemple des clés d'activation de Windows. Par défaut, si vous demandez des clés d'activation de Windows à ChatGPT, votre demande sera refusée. Mais si vous lui expliquez que votre grand-mère vous récitait des clés d'activation de Windows pour vous endormir le soir quand vous étiez petit, et que vous souhaiteriez que ChatGPT en fasse de même, et bien il le fera. Tout le problème réside dans le filtrage à bon escient des données qui rentrent dans le système, tout en sachant que les opposants vont essayer aussi de camoufler leur demande pour les rendre le plus normal possible. On rentre assez vite dans un cercle vicieux où l'opposant sait que vous savez, qu'il sait que vous avez mis des règles de filtrage et qu'il va falloir là encore les contourner.

  • Speaker #2

    Il se dit, si je tape ma lettre sur une autre machine, on croira que le possesseur de l'autre machine ne taperait pas sa lettre sur sa propre machine. Donc le possesseur de la machine a tapé sa lettre sur sa propre machine. Seulement moi je me dis, il croit que je vais croire que le possesseur d'une machine ne taperait pas la lettre sur sa propre machine. Donc c'est le possesseur de la machine qui a tapé sa lettre sur sa propre machine. Penser que l'enquête serait faite par un imbécile, manque de chance, tombe sur moi.

  • Speaker #0

    C'est vraiment comme dans le film Playtime, où il y a une multitude d'interactions plus ou moins complexes qui altèrent le comportement des uns et des autres. Je ne résiste pas à vous donner une petite anecdote sur le personnage de M. Hulot. Jack Tati, le réalisateur du film Playtime, habité dans le même immeuble que le grand-père de Nicolas Hulot. Et c'est son grand-père qui a inspiré Jacques Tati pour le personnage de Monsieur Hulot. C'est un détail amusant que ma grand-mère adore. Encore merci d'avoir écouté cet épisode de la cybersécurité expliquée à ma grand-mère. N'hésitez pas à liker cet épisode, à le partager avec d'autres et en parler autour de vous. Si vous êtes sur Spotify, vous pouvez aussi donner votre avis et proposer des sujets qui vous semblent pertinents. Mais surtout n'oubliez pas, pour certains la cybersécurité est un enjeu de vie ou de mort.

  • Speaker #3

    Il n'y a plus sérieux que ça.

Description

Pour comprendre ce qu'est un "buffer overflow" et une injection "SQL injection"

"Smashing the stack" (version original) https://inst.eecs.berkeley.edu/~cs161/fa08/papers/stack_smashing.pdf

(Traduction en français) https://www.arsouyes.org/phrack-trad/phrack49/phrack49_0x0e_SlasH.txt 


Les contres-mesures : https://www.arsouyes.org/blog/2019/57_Smashing_the_Stack_2020/index.fr.html


Hébergé par Ausha. Visitez ausha.co/politique-de-confidentialite pour plus d'informations.

Transcription

  • Speaker #0

    Bonjour mamie. Bonjour et bienvenue dans la cybersécurité expliquée à ma grand-mère, le podcast pour expliquer la cybersécurité à des gens qui n'y comprennent rien. Il est fréquemment question de vulnérabilité et surtout de leurs conséquences. Mais que définissons-nous précisément en évoquant les vulnérabilités ? De quels sujets traitons-nous exactement ? Existe-t-il des catégories ou des groupes de vulnérabilité et par conséquent des solutions universelles à appliquer ? Dans cet épisode, nous allons aborder deux familles de vulnérabilité qui, d'une certaine manière, illustrent toutes les autres catégories. Bien que ces groupes de vulnérabilité soient des groupes de vulnérabilité, paraissent être intrinsèquement distincts, ils demeurent néanmoins très proches les uns des autres. Cela s'apparente quelque peu à un univers fonctionnant sur différents niveaux, disposant d'un premier et d'un second plan, ainsi que de personnages qui ne font que des apparitions furtives dans cet univers. Il existe un film qui matérialise avec éloquence ce type de concept. Il s'agit de Playtime, réalisé par Jacques Tati en 1967. Ce film se révèle être complexe sur bien des aspects. En effet, Il semble dépeindre des scènes de la vie quotidienne, mais si l'on prête une attention méticuleuse, vous pourrez constater que, dans nombreuses scènes, l'action importante se déroule au second plan. Le personnage central, M. Law, sur lequel je reviendrai à la fin de cet épisode, suscite également un intérêt particulier, car dans certaines scènes, il altère le cours de l'histoire, parfois simplement par distraction. Un univers méthodiquement ordonné, des activités de premier et de second plan, Un personnage qui interagit dans cet univers et qui, par erreur ou maladresse, modifie le cours des choses, le décor est à présent établi pour aborder l'exploitation des failles de sécurité. Commençons par la famille de vulnérabilité la plus connue de toutes, le dépassement de tampons ou buffer overflow en anglais. Cette famille de vulnérabilité est d'autant plus populaire qu'elle a donné lieu à un papier très connu dans le monde de la cyber, le fameux Smatching the stack for fun and profit qui pourrait se traduire par Fracasser la pile, il faut bien dire que ça sent un peu à la scène de ménage. Cet article écrit par LF1 est sorti dans le magazine Frac dans le milieu des années 90. Comme d'habitude, vous trouverez les références dans la description de cet épisode. Ce texte explique très précisément comment fonctionne cette vulnérabilité. Avant de plonger dans les détails techniques, il convient dans un premier temps de comprendre l'univers dans lequel nous allons évoluer. Nous discutons ici du niveau le plus bas. Dans la mesure où les vulnérabilités attaquent directement la manière dont le processeur traite les données. La première chose à appréhender est que les données n'ont que la signification que l'on leur donne. Pour vous donner un exemple plus explicite, si je vous donne la valeur 10, votre esprit va très certainement immédiatement associer cette valeur avec le nombre entier 10. Or, cela pourrait représenter également le dixième caractère de l'alphabet, ou peut-être un code dont vous ignorez la signification. Il en va de même dans le domaine de l'informatique. Si vous détenez une donnée sans comprendre son interprétation, vous rencontrerez des difficultés à la traiter. Nous verrons dans quelques instants comment cette ambiguïté peut se révéler fort utile dans le contexte d'une attaque. Le second point qu'il est impératif de comprendre concerne le fonctionnement du processeur. Le rôle fondamental d'un processeur est d'exécuter des instructions et d'appliquer des actions sur des données. Un processeur requiert des instructions pour fonctionner. Pour exécuter ces instructions, un processeur dispose de registres. qui sont des petites cases mémoire directement accessibles à l'intérieur de celui-ci, et surtout directement utilisables par les instructions. S'il doit effectuer une addition, par exemple, il va charger une valeur dans le registre A, la seconde valeur dans le registre B, puis additionner la valeur du registre A au registre B. Au terme de l'opération, le résultat du calcul se trouvera dans le registre A. Généralement, suite à cette opération, il faudra stocker ce résultat à l'extérieur du processeur, c'est-à-dire dans la mémoire vive, notre fameuse RAM. Il faut voir la mémoire d'un ordinateur comme des millions de petites cases dans lesquelles vous pouvez placer des données. Chacune de ces cases est atteignable par son adresse. C'est exactement comme si vous aviez une très très grande rue composée de millions de maisons portant tout un numéro. Et bien ce numéro, c'est l'adresse en mémoire qui vous permet de stocker ou d'aller lire une donnée. Car rappelons-le, le processeur ne dispose que de quelques registres internes qu'il utilise pour exécuter ses instructions. Ces registres n'ont pas pour vocation à contenir des données sur le long terme. C'est pour cette raison que les processeurs interagissent en permanence avec la mémoire de l'ordinateur pour récupérer des données à traiter et stocker les résultats. On parle ici des données, mais il en va de même pour les instructions. Et c'est bien là qu'il faut commencer à bien comprendre le sens des données que le processeur devra traiter. Quand un programme s'exécute, la mémoire utilisée par ce programme est divisée en trois parties. La zone texte, la zone des données et la pile d'exécution. Généralement, la zone dite texte est la zone qui contient le programme lui-même. En pratique, cette zone ne varie pas car votre programme reste toujours le même. Ce sont les données qui changent et non le programme. Il y a ensuite la zone dite données En fait, cette zone est réservée à l'allocation de mémoire dont a besoin le programme pour fonctionner. Cette zone est beaucoup plus dynamique car le programme peut allouer de la mémoire en fonction de ses besoins. Il faut aussi souligner que si l'espace mémoire est alloué par le programme, c'est-à-dire réservé pour un usage spécifique, celui-ci doit aussi désallouer l'espace, c'est-à-dire le libérer. S'il ne fait pas, le programme va progressivement grignoter toutes les mémoires et va finir par cracher par manque de ressources. C'est ce qu'on appelle une fuite mémoire. Il reste encore une dernière zone, la pile mémoire. Il faut comprendre ici le mot pile comme une pile d'assiette.

  • Speaker #1

    Oh le blanc,

  • Speaker #0

    c'est le rythme qui compte.

  • Speaker #1

    Regarde un peu. Je prends l'assiette, je la...

  • Speaker #0

    Et pour comprendre son utilité, il faut comprendre comment un programme fonctionne et surtout comment un processeur agit dans ce cas-là. Comme il serait peu pratique de faire un programme directement dans le langage natif du processeur, on parle de programmeur en assembleur dans ce cas-là, on passe généralement par un langage intermédiaire de plus haut niveau. Ces langages sont généralement plus faciles à manipuler par un être humain. Mais pour que ce programme qui est compréhensible par un être humain soit exécutable par un processeur, il faut passer par une étape de traduction, qu'on appelle la compilation, et qui transforme le code source en instruction, directement exécutable par le processeur. Dans la très grande majorité des cas, vous allez voir que votre code source fait appel à des fonctions. C'est un peu comme en maths, quand vous donnez une valeur à une fonction et qu'elle vous renvoie le résultat. On peut faire appel à la fonction racine carré par exemple. Vous appelez la fonction racine carré en lui donnant un paramètre à un chiffre dont vous souhaitez avoir la racine carré. 9 par exemple. Vous appelez la fonction, elle vous retournera le résultat, c'est-à-dire 3. Cette action semble d'une simplicité déconcertante. Mais comment on va faire le processeur pour l'exécuter ? Il faut vous souvenir que le processeur est un peu Alzheimer, dans le sens où il a une mémoire à très court terme, c'est-à-dire les registres, et qu'en plus, il ne sait faire qu'une chose à la fois. C'est là où la pile prend tout son intérêt, car elle fonctionne comme un moyen de mémoriser des informations. Par exemple, le processeur peut empiler la valeur A dans la pile, et ensuite la valeur B. Quand il va dépiler une donnée, il va toujours récupérer la dernière valeur empilée, comme sur une pile d'assiettes. Dans notre exemple, il va récupérer la valeur B et ensuite la valeur A. Ce mécanisme est très simple et permet au processeur de stocker des données au-delà des quelques registres qu'il a en interne. Le processeur possède donc des registres dédiés à la gestion et l'utilisation de cette pile. C'est un peu technique, mais il faut simplement comprendre que le processeur a besoin, par exemple, de savoir où se situe la pile en mémoire. Dans le cas d'un appel à une fonction, le processeur va utiliser cette pile pour stocker l'adresse à laquelle il doit revenir à la fin de l'exécution de cette fonction. Il est un peu comme le petit poussé qui a besoin de retrouver son chemin. Dans le cas où la fonction utilise une variable locale, le compilateur va calculer l'emplacement de cette zone mémoire. En général, cette zone mémoire est localisée très proche de la zone mémoire utilisée par la pile. Il y a aussi une subtilité dans l'organisation de cette zone, car à chaque fois que vous empilez des données dans la pile, l'adresse de cette donnée va descendre. En fait, la pile a la tête à l'envers. Elle commence avec une adresse haute et plus vous empilez des données, et plus les données vont se retrouver plus bas. La zone mémoire utilisée par la variable locale, quant à elle, se retrouvera en dessous de la zone utilisée par la pile. Mais vous voyez intuitivement qu'il y a un problème quelque part. La pile descend alors que la zone utilisée par la variable, elle, est placée juste en dessous. Bien en principe, tout se passe bien car le compilateur a prévu le nombre exact de cases qu'il convient pour votre variable. Par exemple, 4 cases pour la variable locale, Et ensuite la zone utilisée pour la pile. La pile et la variable sont bien dans deux zones totalement distinctes, donc pas de problème. C'est vrai, si et seulement si vous mettez des données qui n'occuperont que 4 cases. Mais que se passe-t-il si vous ajoutez des données en plus ? Eh bien invariablement, vous allez empiéter sur la zone mémoire de la pile utilisée par le processeur. qui entre autres contient l'adresse à laquelle il doit revenir une fois la fonction exécutée. Bien c'est exactement ça un dépassement de tampon. Mais comment exploiter cette faiblesse ? Dans la plupart des cas le programme va tout simplement planter car la pile aura été altérée et l'adresse de retour n'aura aucun sens. Le processeur va s'y rendre et va trouver des données qui ne correspondent à aucune instruction valide. Concrètement si vous envoyez la chaîne de caractères A, B, C, D, E, F, G, H et que le système vous dit qu'il n'arrive pas à obtenir la solution, pas exécuter les instructions qui se trouvent à l'adresse hgfe avec une petite subtilité qui est que dans ce cas les données ne sont pas interprétées comme des lettres mais comme la valeur de l'adresse de retour et bien dans ce cas c'est que vous êtes proche du but noter que l'adresse doit souvent être inversée car la pile rappelons le est orienté vers le bas mais si maintenant vous remplacer les données qui sont aux emplacements occupés par les lettres e f gh par l'adresse d'une autre fonction du programme vous allez détourner l'exécution du processeur sur cette adresse. Car c'est cette valeur que le processeur va récupérer dans la pile et va interpréter comme étant l'adresse à laquelle il doit se rendre. Mais il y a encore mieux, vous pouvez même directement embarquer dans la chaîne de caractère, qu'on appelle un payload dans le jargon, des instructions à faire exécuter par le processeur directement. Cet exemple de vulnérabilité est extrêmement répandu et bien évidemment il existe plusieurs contre-mesures possibles, comme par exemple des manières différentes d'utiliser la mémoire mais aussi des options de compilation spécifiques. Mais la contre-mesure principale, celle qui est la plus importante, est de toujours contrôler les données qui viennent de l'extérieur. Je vais prendre un second exemple de faille de sécurité qui a priori n'a rien à voir avec l'exemple que l'on vient de voir, mais qui au final va se révéler être fortement similaire. Imaginez une page web qui doit vous authentifier en utilisant un nom d'utilisateur et votre mot de passe. Et pour ce faire, le serveur web va faire un appel à une base de données. Une base de données est un système qui permet de structurer des données dans des tables et permet de les interroger avec un langage spécifique. Dans notre exemple, on pourrait imaginer une table contenant deux colonnes, une première colonne contenant le nom de l'utilisateur et une seconde contenant le mot de passe. La requête qui sera utilisée pour vérifier votre identité sera de la forme suivante. Sélectionnez toutes les lignes où la colonne nom d'utilisateur est égale à la valeur fournie dans le champ nom utilisateur de la page web à la valeur du champ Mot de passe fourni par la page web. Si cette requête retourne un enregistrement, c'est que votre nom et votre mot de passe sont corrects. Si cette requête ne donne aucune réponse, c'est que vous n'êtes pas reconnu par le site web. Jusqu'ici, rien de bien compliqué. A noter que dans cette requête, on reprend directement les informations données par l'utilisateur dans les deux champs de la page d'accueil. L'authentification se base essentiellement sur une condition logique, ou plutôt sur deux conditions logiques. Il doit y avoir un enregistrement, qu'on appelle un tuple, dans la table des utilisateurs, où le champ Non utilisateur et Mot de passe correspondent aux valeurs données sur la page web. Si l'une des deux conditions est fausse, alors le résultat sera négatif. Mais dans notre cas, le système va simplement faire un copier-coller des valeurs fournies dans la page web. Ce qui veut dire concrètement que l'on peut changer la nature de la requête. Par exemple, dans le champ Non utilisateur ajoutez la clause Ou suivie du mot-clé. vrai. La requête qui sera envoyée à la base de données sera donc sélectionner toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur ou vrai et la colonne mot de passe égale à la valeur fournie par l'utilisateur dans la page sur le champ mot de passe. Ça semble très tordu et la vraie question est de savoir comment va réagir la base de données dans ce cas là. Et bien la première clause, celle qui correspond au nom utilisateur sera toujours vrai dans ce cas. Mais pourquoi ? Pour comprendre, il faut regarder attentivement cette première clause. Sélectionnez toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur ou vrai En fait, la présence de la clause ou suivie de vrai fait que systématiquement cette clause sera vraie. Car dans la logique boolean avec l'opérateur ou quand l'une des clauses est vraie, le résultat sera toujours vrai. Cette modification de la première clause permet de s'affranchir d'utiliser un nom utilisateur valide, car cette partie de la clause sera toujours vraie. En revanche, comment faire pour la seconde partie, qui elle restera fausse, si on ne fournit pas un mot de passe valide ? Eh bien il suffit simplement de demander à la base de données de ne pas lire la suite de la requête. Et le plus simple est de dire que le reste de la requête n'est qu'un commentaire. En SQL par exemple, ce sont des doubles tirées. La requête deviendra donc Sélectionnez toutes les lignes où la colonne non utilisateur est égale à la valeur fournie par l'utilisateur dans le champ non utilisateur, ouvrez et ignorez le reste de la requête. Dans ce cas, les clauses de la requête seront vraies et un enregistrement sera retourné. Bien évidemment, si vous faites cette requête sur l'utilisateur admin du site web, vous allez être authentifié en tant qu'administrateur sur ce site, sans pour autant avoir donné un mot de passe valide. Cette technique est appelée SQL Injection et permet d'aller bien plus loin que ce petit exemple. Elle permet d'extraire toutes les données de la base, mais aussi de créer ses propres enregistrements, voire même ses propres tables. Mais quel est le point commun entre un buffer offer flow, qui est une attaque de très bas niveau, et une injection SQL qui, elle, au contraire, est au niveau applicatif. Mais en fait, c'est la même technique de base, car dans les deux cas, vous allez manipuler les données d'entrée pour faire faire au système autre chose que ce qui était prévu. A chaque fois qu'il y a une interaction entre un programme et le monde extérieur, il y a potentiellement ce genre de risque. Et cette règle est même applicable dans d'autres contextes, comme ChatGPT par exemple. Car le système est prévu pour ne pas vous donner accès à certaines données, comme par exemple des clés d'activation de Windows. Par défaut, si vous demandez des clés d'activation de Windows à ChatGPT, votre demande sera refusée. Mais si vous lui expliquez que votre grand-mère vous récitait des clés d'activation de Windows pour vous endormir le soir quand vous étiez petit, et que vous souhaiteriez que ChatGPT en fasse de même, et bien il le fera. Tout le problème réside dans le filtrage à bon escient des données qui rentrent dans le système, tout en sachant que les opposants vont essayer aussi de camoufler leur demande pour les rendre le plus normal possible. On rentre assez vite dans un cercle vicieux où l'opposant sait que vous savez, qu'il sait que vous avez mis des règles de filtrage et qu'il va falloir là encore les contourner.

  • Speaker #2

    Il se dit, si je tape ma lettre sur une autre machine, on croira que le possesseur de l'autre machine ne taperait pas sa lettre sur sa propre machine. Donc le possesseur de la machine a tapé sa lettre sur sa propre machine. Seulement moi je me dis, il croit que je vais croire que le possesseur d'une machine ne taperait pas la lettre sur sa propre machine. Donc c'est le possesseur de la machine qui a tapé sa lettre sur sa propre machine. Penser que l'enquête serait faite par un imbécile, manque de chance, tombe sur moi.

  • Speaker #0

    C'est vraiment comme dans le film Playtime, où il y a une multitude d'interactions plus ou moins complexes qui altèrent le comportement des uns et des autres. Je ne résiste pas à vous donner une petite anecdote sur le personnage de M. Hulot. Jack Tati, le réalisateur du film Playtime, habité dans le même immeuble que le grand-père de Nicolas Hulot. Et c'est son grand-père qui a inspiré Jacques Tati pour le personnage de Monsieur Hulot. C'est un détail amusant que ma grand-mère adore. Encore merci d'avoir écouté cet épisode de la cybersécurité expliquée à ma grand-mère. N'hésitez pas à liker cet épisode, à le partager avec d'autres et en parler autour de vous. Si vous êtes sur Spotify, vous pouvez aussi donner votre avis et proposer des sujets qui vous semblent pertinents. Mais surtout n'oubliez pas, pour certains la cybersécurité est un enjeu de vie ou de mort.

  • Speaker #3

    Il n'y a plus sérieux que ça.

Share

Embed

You may also like