Existe-t-il des langages de programmation qui sont conçus pour être robustes contre le piratage?
En d'autres termes, une application peut être piratée en raison d'une implémentation cassée, même si la conception est parfaite. Je cherche à réduire le risque qu'un développeur implémente incorrectement une spécification.
Par exemple
Heartbleed ne serait pas arrivé si le langage utilisé pouvait se prémunir contre un Buffer Over-Read .
Les injections SQL pourraient ne pas se produire s'il y avait un moyen imposé par la langue pour encoder/décoder les données HTML
Les données sensibles peuvent être enregistrées dans des fichiers de page dans certaines langues où les contrôles de bas niveau de effacement sécurisé de la mémoire ne sont pas disponibles.
Les problèmes/débordements de pointeur se produisent plus souvent en C par rapport au code managé
Erreurs d'arrondi numériques peut se produire lorsque le développeur utilise le mauvais type de données pour les mauvaises données
Les attaques par déni de service peuvent être réduites si l'application est correctement multi-thread
signature de code peut réduire la menace de problèmes de sécurité d'exécution ( link , link )
Question
Edit: Beaucoup de gens ont abordé le problème de débordement de tampon, ou disent que le programmeur est responsable de la sécurité. J'essaie juste de me faire une idée s'il existe des langues dont le but principal était de se prêter à la sécurité autant que possible et raisonnable. Autrement dit, certaines langues ont-elles des fonctionnalités qui les rendent nettement plus (ou moins) sécurisées que la plupart des autres langues?
Le langage Ada est conçu pour éviter autant que possible les erreurs de programmation courantes et est utilisé dans les systèmes critiques où un bogue système peut avoir des conséquences catastrophiques.
Quelques exemples où Ada va au-delà de la sécurité intégrée typique fournie par d'autres langages modernes:
Le type de plage entière permet de spécifier une plage autorisée pour un entier. Toute valeur en dehors de cette plage lèvera une exception (dans les langues qui ne prennent pas en charge un type de plage, une vérification manuelle devra être effectuée).
:=
pour l'affectation =
pour les contrôles d'égalité. Cela évite l'écueil courant dans les langues qui utilisent =
pour affectation et ==
pour l'égalité d'affectation accidentelle lors d'une vérification d'égalité (dans Ada, une affectation accidentelle ne se compilerait pas).
in
et out
paramètres qui spécifient si un paramètre de méthode peut être lu ou écrit
évite les problèmes avec les niveaux d'indentation des groupes d'instructions (par exemple le récent bogue SSL Apple ) en raison de l'utilisation du mot clé end
les contrats (depuis Ada 2012, et précédemment dans le sous-ensemble SPARK) permettent aux méthodes de spécifier les conditions préalables et postconditions qui doivent être satisfaites)
Il y a d'autres exemples de la façon dont Ada a été conçu pour la sécurité fournie dans le Safe and Secure Booklet (PDF).
Bien sûr, bon nombre de ces problèmes peuvent être atténués par un style de codage approprié, une révision du code, des tests unitaires, etc., mais les faire au niveau de la langue signifie que vous l'obtenez gratuitement.
Il convient également d'ajouter que, malgré le fait qu'un langage conçu pour la sécurité comme Ada supprime de nombreuses classes de bogues, rien ne vous empêche d'introduire des bogues de logique métier dont le langage ne sait rien.
En fait la plupart les langues sont "sécurisées" en ce qui concerne les dépassements de tampon. Ce qu'il faut pour qu'un langage soit "sécurisé" à cet égard, c'est la conjonction de: types stricts, vérifications systématiques liées aux tableaux et gestion automatique de la mémoire (un "garbage collector"). Voir cette réponse pour plus de détails.
Quelques anciens langages ne sont pas "sécurisés" dans ce sens, notamment C (et C++), ainsi que Forth, Fortran ... et, bien sûr, Assembly. Techniquement, il est possible d'écrire une implémentation de C qui serait "sûre" et toujours formellement conforme à la norme C, mais à un prix élevé (par exemple, vous devez faire free()
aucune opération, donc la mémoire allouée est allouée "pour toujours"). Personne ne fait ça.
Les langages "sécurisés" (en ce qui concerne les débordements de tampon) incluent Java, C #, OCaml, Python, Perl, Go, voire PHP. Certains de ces langages sont plus qu'efficaces pour implémenter SSL/TLS (même sur les systèmes embarqués - je parle d'expérience). Bien qu'il soit possible d'écrire du code C sécurisé, il faut (beaucoup) de concentration et de compétence, et l'expérience montre à plusieurs reprises que c'est difficile, et que même les meilleurs développeurs ne peuvent pas prétendre qu'ils = toujours appliquer les niveaux de concentration et de compétence requis. C'est une expérience humiliante. L'affirmation "n'utilisez pas C, c'est dangereux" est impopulaire, non pas parce que ce serait faux, mais, bien au contraire, parce que c'est vrai: elle oblige les développeurs à faire face à l'idée qu'ils pourraient ne pas être les demi-dieux de une programmation qu'ils croient être, au plus profond de l'intimité de leur âme.
Notez cependant que ces langages "sécurisés" n'empêchent pas le bug: un débordement de tampon est toujours un comportement indésirable. Mais ils contiennent les dégâts: la mémoire au-delà du tampon n'est pas réellement lue ou écrite; au lieu de cela, le thread incriminé déclenche une exception et est (généralement) terminé. Dans le cas de heartbleed, cela aurait évité que le bogue ne devienne une vulnérabilité et cela aurait pu aider à éviter la panique à grande échelle que nous avons observée ces derniers jours (personne ne sait vraiment quoi fait qu'une vulnérabilité aléatoire devienne virale comme une vidéo Youtube mettant en vedette un cheval invisible coréen; mais, "logiquement", si ce n'était pas une vulnérabilité, cela aurait dû éviter toute cette tragicomédie).
Edit: comme il a été abondamment discuté dans les commentaires, j'ai réfléchi au problème de la gestion de la mémoire sécurisée pour C, et il existe une sorte de solution qui permet toujours à free()
de fonctionner, mais il y a une triche.
On peut imaginer un compilateur C qui produit des "gros pointeurs". Par exemple, sur une machine 32 bits, créez des pointeurs sur des valeurs 96 bits. Chaque bloc alloué se verra attribuer un identifiant unique de 64 bits (par exemple, un compteur), et une structure de mémoire interne (table de hachage, arbre équilibré ...) est maintenue qui référence tous les blocs par ID. Pour chaque bloc, sa longueur est également enregistrée dans la structure. Une valeur de pointeur est alors la concaténation de l'ID de bloc et un décalage à l'intérieur de ce bloc. Lorsqu'un pointeur est suivi, le bloc est localisé par ID, le décalage est comparé à la longueur du bloc et ce n'est qu'ensuite que l'accès est effectué. Cette configuration résout double-free et use-after-free. Il détecte également la plupart dépassements de tampon (mais pas tous: un tampon peut faire partie d'une structure plus grande, et la gestion de malloc()
/free()
uniquement voit les blocs extérieurs).
Le "cheat" est le "compteur 64 bits unique". Cela n'est vrai que tant que vous ne manquez pas d'entiers 64 bits; au-delà, vous devez réutiliser les anciennes valeurs. 64 bits devraient éviter ce problème dans la pratique (il faudrait des années pour "boucler"), mais un compteur plus petit (par exemple 32 bits) pourrait s'avérer être un problème.
De plus, bien sûr, la surcharge pour les accès à la mémoire peut être non négligeable (quelques lectures physiques pour chaque accès, bien que certains cas puissent être optimisés), et doubler la taille du pointeur implique également une utilisation de la mémoire plus élevée pour les structures riches en pointeurs . Je ne connais aucun compilateur C existant qui applique une telle stratégie; c'est purement théorique en ce moment.
La plupart des langages de programmation de niveau supérieur à C sont beaucoup plus sûrs lorsqu'il s'agit d'erreurs de programmation comme Heartbleed. Les exemples qui compilent principalement en code machine incluent D, Rust et Ada. À mon avis, il n'est pas intéressant de parler de la sécurité de la mémoire uniquement).
Voici une liste de fonctionnalités supplémentaires du langage de programmation qui (je pense) rendent beaucoup plus difficile l'écriture de code dangereux. Les cinq premières fonctionnalités étendent les capacités du compilateur à raisonner sur votre code, donc vous , un être humain sujet à l'erreur, n'avez pas à *. En outre, ces fonctionnalités devraient également permettre à un autre être humain, un auditeur, de raisonner plus facilement sur votre code. Le code source d'OpenSSL est souvent décrit comme un gâchis et un langage plus strict que C aurait pu aider à le rendre plus facile à raisonner. Les deux dernières fonctionnalités concernent les problèmes de contexte qui affectent également la sécurité.
De ma connaissance limitée des langues, aucune langue ne fait tout cela. Rust est un exemple de langage qui en couvre plusieurs - il est strict, immuable par défaut, a une sécurité limitée, ne nécessite pas de ramassage des ordures et est assez performant et portable. La vérification de la souillure au moment de la compilation et les types dépendants semblent actuellement être des fonctionnalités exotiques qui nécessitent malheureusement des outils d'analyse de code statique supplémentaires ou de nouveaux langages.
* Voir aussi: vérification formelle
Dans l'esprit général de ce que vous demandez, je pense que le langage E (la "plate-forme sécurisée distribuée d'objets purs et le langage de script p2p") est assez intéressant, en ce sens qu'il tente d'offrir en toute sécurité fonctionnalités/modèles de calcul généralement non disponibles.
Tous les langages de programmation actuels (c'est-à-dire toujours mis à jour) sont conçus pour avoir le moins de failles de sécurité inhérentes possible, mais à la fin de la journée c'est (presque toujours) le programmeur qui est responsable des failles de sécurité, pas le langage qu'il est en utilisant.
EDIT: Comme l'a souligné @DCKing, toutes les langues ne sont pas égales, et je ne dis pas que c'est une bonne idée d'en choisir une au hasard et d'essayer de la faire fonctionner. Je dis qu'un programmeur C (très) talentueux peut rendre un programme aussi sûr qu'un programme sémantiquement identique écrit dans un langage de niveau supérieur. Mon point est que nous devons reconnaître que certaines langues facilitent les erreurs, mais aussi savoir qu'en fin de compte, c'est l'erreur du programmeur, pas celle du langage (à quelques exceptions près)
Il n'y a pas de langage sécurisé. Si une langue offre une sécurité suffisante pour votre problème, cela dépend beaucoup du problème que vous essayez de résoudre. Comme si vous écrivez une application Web, la sécurité de la plupart des langues utilisées dans ce contexte (par exemple Java, PHP, JavaScript ... ajoutez votre favori) est suffisante pour éviter des choses comme les débordements de tampon, mais même les langues les plus fortement typées ne le font pas offrent un support inhérent pour des choses spécifiques au Web, comme rendre impossible ou du moins difficile d'introduire des bogues de Cross-Site-Scripting ou similaires. Et aucune langue ne vous protégera contre un modèle de mauvaise confiance, comme la confiance des serveurs DNS (reliure DNS, etc.), le modèle PKI actuel ou en incluant des scripts tiers (par exemple hors de votre contrôle) dans votre application Web (généralement des publicités ou des analyses Google) .
Le choix d'une langue appropriée pourrait donc vous aider un peu, mais il n'y a pas d'épée de sécurité magique.
N'oubliez pas que pour la plupart des langages de programmation, vous devez vous soucier de la sécurité des deux langages. Il y a la langue que vous utilisez réellement, puis la langue dans laquelle le compilateur ou l'interpréteur est écrit, ce qui est souvent différent. (Techniquement, il y en a un troisième, qui est le microcode du CPU lui-même.) Un problème de sécurité dans soit de ces langues peut rendre votre programme peu sûr.
Il existe de nombreuses langues sécurisées. Je dirais qu'un langage avec gestion de la mémoire et sécurité des threads est aussi sûr qu'un langage peut l'être.
Cependant, la plupart d'entre eux sont inefficaces. La collecte des ordures est coûteuse, et les langues interprétées plus encore. Et c'est pourquoi les grandes applications à ce jour sont écrites dans le C/C++ non sécurisé en mémoire.
J'ai récemment joué avec Rust , et il me semble que c'est un langage "sécurisé" dans le sens où il a été en partie conçu pour cela.
C'est un langage compilé comme C++, et il propose également des pointeurs et des accès concurrents. (Le ramasse-miettes n'est pas nécessaire)
Cependant, il ne supporte pas la sécurité de la mémoire des pointeurs et la concurrence avec lui. Rust est un langage qui ne fait pas confiance au programmeur et au moment de la compilation, il vérifie l'utilisation suspecte des pointeurs. Il existe plusieurs types de pointeurs/références (empruntés, possédés, etc.), et certains d'entre eux ont des règles strictes à leur sujet. Par exemple, on ne peut pas:
Il existe des règles similaires qui garantissent la sécurité des threads. Si on le souhaite, ils peuvent contourner un grand nombre de ces vérifications en utilisant des cases non sécurisées ("croyez-moi, je sais ce que je fais"), ou ralentissez les pointeurs récupérés. Il existe également des moyens plus rustiques (et efficaces) de le faire en utilisant une combinaison de clones et de références, qui varient en fonction de l'utilisation.
Premièrement, vous n'écrivez pas réellement de programmes dans des langages de programmation. Vous écrivez instructions pour le compilateur qui décrivez quel type de programme vous voulez, et le compilateur produit un programme, à sa manière particulière, qui, espérons-le (si votre compilateur est bien conçu) font la même chose que votre code source décrit. Tous les programmes, lorsqu'ils sont en cours d'exécution, sont en "langage machine" - ce sont une série de nombres qui sont interprétés d'une certaine manière lorsqu'ils sont chargés dans RAM et introduits dans le CPU. Le langage machine est pas conçu avec la robustesse au piratage à l'esprit, donc aucun langage qui est compilé ne peut être vraiment "résistant" au piratage, car le programme réel sera de toute façon en langage machine. Tout langage interprété ou VM toujours exécuté dans un framework natif qui est finalement compilé en langage machine, donc le problème persiste.
Deuxièmement, la plupart des langues réelles sont Turing complètes. Cela signifie que toute tâche qui peut être accomplie par l'un d'eux peut l'être par tous. Par conséquent, vous ne pouvez pas rendre le "piratage" impossible (si le piratage signifie écrire des programmes malveillants); cela briserait l'exhaustivité de Turing.
Il vaut la peine de clarifier à ce stade ce que vous entendez par piratage. Puisque vous mentionnez Heartbleed, j'imagine que vous ne le pensez pas dans le sens de Stallman ("bricolage ludique").
Si vous voulez dire des gens qui écrivent des programmes qui accèdent directement à la mémoire et volent des données, ou qui modifient d'autres programmes (tels que des virus ou des enregistreurs de frappe), ce n'est pas un problème qu'une langue peut vraiment gérer. Un compilateur peut aider, en ayant une fonction supplémentaire pour produire du code machine obscurci lors de la compilation, mais en fin de compte, il est toujours possible pour un pirate de mémoire habile de trouver son chemin. La solution à ce problème est la conception du système d'exploitation: un système d'exploitation doit mettre des programmes en sandbox et ne pas permettre à un programme de gâcher la mémoire appartenant à un autre programme. Cela fait partie de ce que fait l'UAC dans Windows (bien que Sandboxie soit un meilleur exemple).
Il y a une mise en garde ici: certains langages, comme C # ou Java ont des fonctionnalités (plus correctement, le compilateur et le VM dans lequel les programmes exécutés ont des fonctionnalités)) qui vérifient si un programme essaie de se cacher dans la mémoire d'un autre programme, et lorsque cela se produit, lancez des erreurs comme IllegalAccessException
(par exemple, keylogger.exe
ne devrait pas être en mesure de lire le Credit_card_number
value from internet banking application.exe
). Bien sûr, cela nécessite de garder une trace de la mémoire qui appartient à quel programme, ce qui a des performances et des coûts d'effort non triviaux. Certains langages "plus simples" comme C ne l'ont pas - c'est pourquoi beaucoup de hacks comme les virus sont écrits en C. De nos jours, vous devez être intelligent pour éviter l'UAC, mais à l'époque de Windows 98, les gens pouvaient faire toutes sortes de choses folles sur votre ordinateur/système d'exploitation en lisant et en écrivant à mémoire, ils n'étaient pas censés le faire. Notez que même en C #, vous avez toujours la possibilité d'utiliser des pointeurs normaux de type C (que les langages appellent unsafe
et vous obligent à marquer comme tel dans la morue e) si vous le souhaitez - bien que CLR contiendra probablement votre hack en lui-même, à moins que vous ne trouviez une faille de sécurité dans le CLR qui vous permette de pénétrer dans le reste de la mémoire.
Le deuxième type de piratage consiste à exploiter un bogue dans un programme existant. Il s'agit de la catégorie à laquelle Heartbleed appartient. Avec cela, la question est de savoir si le programmeur fait une erreur ou non. Évidemment, si votre langue est quelque chose comme Brainfuck ou Perl qui est très difficile à lire, il est probable que vous fassiez des erreurs. S'il s'agit d'un langage avec de nombreux "gotcha" comme C++ (voir "classique" if (i=1)
vs if (i==1)
ou le concours d'obscurcissement C) alors il peut être difficile de détecter les erreurs. En ce sens, la conception pour la sécurité n'est vraiment qu'un cas spécial trivial de conception pour minimiser les erreurs de programmation.
Notez que le bug Heartbleed, qu'il s'agisse d'un sabotage délibéré ou d'une erreur honnête, était un problème avec algorithme utilisé (l'auteur "a oublié" de vérifier la taille) - donc aucun compilateur à court d'une IA aussi intelligente qu'un un humain très intelligent pourrait espérer le détecter; bien que la violation d'accès qui en résulte aurait pu être rattrapée par une gestion intelligente de la mémoire.
Il existe deux sortes de préoccupations concernant le piratage:
Un programme a été programmé par erreur et vous permet de faire des choses que vous ne devriez pas faire. Par exemple. Le serveur Gmail permet à tout le monde de voir vos e-mails, au lieu de leur demander de saisir d'abord le nom d'utilisateur et le mot de passe corrects, car quelqu'un a fait une erreur lors du développement du logiciel serveur. Comprend les bogues, les vulnérabilités, etc.
Un programme est manipulé par le programme malveillant du pirate. Comprend les virus, les enregistreurs de frappe et autres logiciels malveillants.
(1) peut être corrigé en rendant un langage plus strict et explicite, de sorte que la détection des erreurs est plus facile, mais en fin de compte, seules des erreurs très simples peuvent être détectées par des outils automatisés, et comme pour les "tripwires" comme la vérification de portée d'Ada, on peut faire valoir que la reconnaissance de la possibilité d'une erreur est nécessaire pour que vous pensiez à ajouter le chèque en premier lieu, et la reconnaissance de la possibilité est déjà la partie la plus difficile.
(2) ne peut pas être corrigé en changeant la langue. Si vous créez un langage dans lequel il est très difficile d'écrire des applications malveillantes, les pirates utiliseront simplement un autre langage et n'auront aucune difficulté supplémentaire à manipuler des programmes écrits dans votre langage car ils sont finalement exécutés comme code machine de toute façon. Il peut être corrigé en créant un système d'exploitation qui surveille très attentivement les programmes qui y sont exécutés, mais il s'agit alors de problèmes de type (1) dans le code source du système d'exploitation.
Les langages sécurisés de type géré font beaucoup pour empêcher ce genre de chose en fournissant automatiquement la validation des types et en éloignant l'exécution du code du CPU lui-même, mais cela n'exclut pas la possibilité de bogues dans l'implémentation du système que le langage utilise pour mapper à la CPU (par exemple, le CLR en .Net ou la JVM en Java). Il n'exclut pas non plus la possibilité de bogues dans une application qui pourraient la rendre vulnérable à la manipulation ou à la fuite de données pour elle-même.
Ils améliorent considérablement la sécurité du système, mais ils sont également plus volumineux, plus lents et plus limités en fonction de la surcharge du moteur d'exécution qu'ils doivent exécuter pour fournir cette fonctionnalité.
Il existe certainement des langues conçues pour être sécurisées, mais aucune n'est parfaite. Par exemple, Ada vous permet de spécifier une plage autorisée pour les variables entières et lève une exception si elles sortent de cette plage. Sonne bien, vous évite d'avoir à vérifier manuellement. Le problème est que si vous n'avez pas à vérifier manuellement, il est facile de configurer ce mécanisme, puis d'oublier de considérer les conséquences, c'est-à-dire les exceptions d'entier hors plage. Vous venez de créer un vecteur pour les attaques par déni de service.
La sécurité est un processus. Le langage peut aider, mais au mieux, il ne peut que réduire le risque d'erreurs et en crée généralement de nouvelles et souvent encore plus subtiles. On peut dire que C, de par sa nature, est plus facile à comprendre et fonctionne de manière entièrement déterministe (pas de collecte de déchets en arrière-plan, par exemple), est idéal pour écrire du code sécurisé. Et bien sûr, lorsque vous regardez beaucoup de code critique pour la sécurité, il est écrit en C. Pour être juste envers Ada, il s'agit plus de fiabilité que de sécurité.