La plupart des langues modernes (qui sont en quelque sorte interprétées) ont une sorte de fonction eval . Une telle fonction exécute un code de langue arbitraire, la plupart du temps passé comme argument principal sous forme de chaîne (différentes langues peuvent ajouter plus de fonctionnalités à la fonction eval).
Je comprends que les utilisateurs ne devraient pas être autorisés à exécuter cette fonction ( éditer c'est-à-dire prendre directement ou indirectement une entrée arbitraire d'un utilisateur arbitraire à passer à eval
), en particulier avec côté serveur logiciels, car ils pourraient forcer le processus à exécuter du code malveillant. De cette façon, les tutoriels et les communautés nous disent de ne pas utiliser eval. Cependant, il existe de nombreuses fois où eval est utile et utilisé:
ir.rule
qui peut utiliser dynamique python).Ils ont donc une bonne utilisation, tant qu'ils sont utilisés correctement. Le principal avantage est que la fonctionnalité permet aux administrateurs d'écrire du code personnalisé sans avoir à créer plus de fichiers et à les inclure (bien que la plupart des frameworks utilisant les fonctionnalités eval aient également un moyen de spécifier un fichier, un module, un package, ... à lire).
Cependant, l'eval est mauvais dans la culture populaire. Des trucs comme pénétrer dans votre système vous viennent à l'esprit.
Cependant, il existe d'autres fonctions qui pourraient être nuisibles si elles étaient accessibles aux utilisateurs: dissociation, lecture, écriture (sémantique des fichiers), allocation de mémoire et arithmétique des pointeurs, accès au modèle de base de données (même si l'on ne tient pas compte des cas injectables SQL).
Donc, fondamentalement, la plupart du temps quand tout code n'est pas écrit correctement ou non regardé correctement (ressources, utilisateurs, environnements, ...), le code est diabolique et peut même avoir un impact économique.
Mais il y a quelque chose de spécial avec les fonctions eval
(quelle que soit la langue).
Question: Y a-t-il un fait historique pour que cette peur fasse partie de la culture populaire, au lieu de porter la même attention aux autres caractéristiques potentiellement dangereuses?
Une fonction eval
en elle-même n'est pas mauvaise, et il y a un point subtil que je ne crois pas que vous faites:
J'ai écrit du code qui utilisait une fonction de type eval
et il était sécurisé: le programme et les paramètres étaient codés en dur. Parfois, il n'y a pas de fonction de langue ou de bibliothèque pour faire ce dont le programme a besoin et exécuter une commande Shell est le chemin court. "Je dois terminer le codage en quelques heures, mais l'écriture de code Java/.NET/PHP/quel que soit le code prendra deux jours. Ou je peux eval
en cinq minutes."
Une fois que vous autorisez les utilisateurs à exécuter tout ce qu'ils veulent, même s'ils sont verrouillés par des privilèges d'utilisateur ou derrière un écran "sécurisé", vous créez des vecteurs d'attaque. Chaque semaine, certains CMS aléatoires, logiciels de blogs, etc. ont un trou de sécurité corrigé où un attaquant peut exploiter un trou comme celui-ci. Vous comptez sur la pile logicielle entière pour protéger l'accès à une fonction qui peut être utilisée pour rm -rf /
ou autre chose catastrophique (note: cette commande a peu de chances de réussir, mais échouera après causant un peu de dégâts).
Y a-t-il un fait historique pour que cette peur fasse partie de la culture populaire, au lieu de porter la même attention aux autres caractéristiques potentiellement dangereuses?
Oui, il existe un précédent historique. En raison des nombreux bugs qui ont été corrigés au fil des ans dans divers logiciels qui permettent à des attaquants distants d'exécuter du code arbitraire, l'idée de eval
est généralement tombée en désuétude. Les langages et bibliothèques modernes ont de riches ensembles de fonctionnalités qui rendent eval
moins important, et ce n'est pas un hasard. Il rend les fonctions plus faciles à utiliser et réduit le risque d'exploiter.
Une grande attention a été accordée à de nombreuses fonctionnalités potentiellement non sécurisées dans les langues populaires. Que l'on reçoive plus d'attention est principalement une question d'opinion, mais les fonctionnalités eval
ont certainement un problème de sécurité prouvable facile à comprendre . D'une part, ils permettent d'exécuter des commandes du système d'exploitation, y compris des programmes intégrés Shell et des programmes externes standard (par exemple rm
ou del
). Deuxièmement, combiné à d'autres exploits, un attaquant peut être en mesure de télécharger son propre exécutable ou script Shell, puis de l'exécuter via votre logiciel, ouvrant la porte à presque tout ce qui se passe (rien de bon).
C'est un problème difficile. Le logiciel est complexe, et une pile de logiciels (par exemple [[# #]] lampe [~ # ~] ) est plusieurs pièces de logiciels qui interagissent les uns avec les autres de manière complexe. Faites attention à la façon dont vous utilisez les fonctionnalités de langage comme celle-ci et ne permettez jamais aux utilisateurs d'exécuter des commandes arbitraires.
Cela tient en partie au fait que la nuance est difficile. Il est facile de dire quelque chose comme ne jamais utiliser goto, les champs publics, l'interpolation de chaînes pour les requêtes SQL ou eval. Ces déclarations ne doivent pas vraiment être comprises comme disant qu'il n'y a jamais, en aucune circonstance, de raison de les utiliser. Mais les éviter en règle générale est une bonne idée.
Eval est fortement déconseillé car il combine plusieurs problèmes courants.
Premièrement, il est sensible aux attaques par injection. Ici, c'est comme une injection SQL en ce sens que lorsque des données contrôlées par l'utilisateur sont insérées dans le code, il est facile de permettre accidentellement l'insertion de code arbitraire.
Deuxièmement, les débutants ont tendance à utiliser eval pour contourner le code mal structuré. Un codeur débutant peut écrire du code qui ressemble à ceci:
x0 = "hello"
x1 = "world"
x2 = "how"
x3 = "are"
x4 = "you?"
for index in range(5):
print eval("x" + index)
Cela fonctionne, mais c'est vraiment la mauvaise façon de résoudre ce problème. De toute évidence, l'utilisation d'une liste serait bien meilleure.
Troisièmement, eval est généralement inefficace. Beaucoup d'efforts sont consacrés à accélérer nos implémentations de langage de programmation. Mais l'évaluation est difficile à accélérer et son utilisation aura généralement des effets néfastes sur vos performances.
Donc, l'eval n'est pas mal. On pourrait dire que l'eval est mauvais, car bon, c'est une façon accrocheuse de le dire. Tout codeur débutant doit strictement rester à l'écart d'eval car quoi qu'il veuille faire, eval est presque certainement la mauvaise solution. Pour certains cas d'utilisation avancés, eval a du sens, et vous devez l'utiliser, mais évitez évidemment les pièges.
Cela revient à dire que "l'exécution de code arbitraire" est un discours technique pour "être capable de tout". Si quelqu'un est capable d'exploiter l'exécution de code arbitraire dans votre code, c'est littéralement la pire vulnérabilité de sécurité possible, car cela signifie ils sont capables de faire tout ce qui est possible pour votre système.
"D'autres bogues potentiellement nuisibles" peuvent bien avoir des limites, ce qui signifie qu'ils sont, par définition, capables de causer moins de dommages qu'une exécution de code arbitraire exploitée.
Il y a une raison pratique et théorique.
La raison pratique est que nous observons que cela cause fréquemment des problèmes. Il est rare que l'évaluation aboutisse à une bonne solution, et il en résulte souvent une mauvaise solution où vous en auriez obtenu une meilleure à la fin si vous aviez prétendu que l'évaluation n'existait pas et abordiez le problème différemment. Donc, le conseil simplifié est de l'ignorer, et si vous venez avec un cas où vous voulez ignorer le conseil simplifié, eh bien, espérons que vous y avez suffisamment réfléchi pour comprendre pourquoi le conseil simplifié n'est pas applicable et le commun les pièges ne vous affecteront pas.
La raison plus théorique est que s'il est difficile d'écrire du bon code, il est encore plus difficile d'écrire code qui écrit du bon code. Que vous utilisiez eval, ou que vous génériez des instructions SQL en collant des chaînes ou en écrivant un compilateur JIT, ce que vous tentez est souvent plus difficile que prévu. Le potentiel d'injection de code malveillant est une grande partie du problème, mais à part cela, il est généralement plus difficile de savoir si votre code est correct si votre code n'existe même pas jusqu'à l'exécution. Le conseil simplifié est donc de vous faciliter la tâche: "utilisez les requêtes SQL paramétrées", "n'utilisez pas eval".
Prenons votre exemple d'effets de sorts: c'est une chose de construire un compilateur ou interprète Lua (ou autre) dans votre jeu afin de permettre aux concepteurs de jeux un langage plus simple que C++ (ou autre) pour décrire les effets de sorts. La plupart des "problèmes d'eval" ne s'appliquent pas si tout ce que vous faites est d'évaluer le code qui a été écrit et testé et inclus dans le jeu ou dans le DLC ou ce que vous avez. C'est juste mélanger les langues. Les gros problèmes vous frappent lorsque vous essayez de générer Lua (ou C++, ou SQL, ou des commandes Shell, ou autre) à la volée, et de le gâcher.
Non, il n'y a pas de fait historique évident.
Les maux de l'eval sont visibles dès le début. D'autres caractéristiques sont légèrement dangereuses. Les gens peuvent supprimer des données. Les gens peuvent voir des données qu'ils ne devraient pas voir. Les gens peuvent écrire des données qu'ils ne devraient pas. Et ils ne peuvent faire la plupart de ces choses que si vous vous trompez et ne validez pas l'entrée utilisateur.
Avec eval, ils peuvent pirater le pentagone et donner l'impression que vous l'avez fait. Ils peuvent inspecter vos frappes pour obtenir vos mots de passe. En supposant un langage complet de Turing, ils peuvent littéralement faire tout ce que votre ordinateur est capable de faire.
Et vous ne pouvez pas valider l'entrée. C'est une chaîne arbitraire de forme libre. La seule façon de le valider serait de construire un analyseur et un moteur d'analyse de code pour la langue en question. Bonne chance avec ça.
Je pense que cela se résume aux aspects suivants:
Salut, j'ai écrit cet outil d'édition d'image extrêmement cool (disponible pour 0,02 $). Après avoir ouvert l'image, vous pouvez passer une multitude de filtres sur votre image. Vous pouvez même en écrire vous-même en utilisant Python (le programme dans lequel j'ai écrit l'application). Je vais simplement utiliser
eval
sur votre entrée, en vous faisant confiance pour être un respectable utilisateur.(plus tard)
Merci de l'acheter. Comme vous pouvez le voir, il fonctionne exactement comme je l'avais promis. Oh, tu veux ouvrir une image? Non, tu ne peux pas. Je n'utiliserai pas la méthode
read
car elle est quelque peu précaire. Économie? Non, je n'utiliserai paswrite
.
Ce que j'essaie de dire, c'est: vous avez besoin de lire/écrire pour presque les outils de base. Idem pour stocker les meilleurs scores de votre jeu oh-so-awesome.
Sans lecture/écriture, votre éditeur d'images est inutile. Sans eval
? Je vais vous écrire un plugin personnalisé pour ça!
De nombreuses méthodes peuvent être potentiellement dangereuses. Comme, par exemple, les read
et write
. Un exemple courant est un service Web vous permettant de lire des images à partir d'un répertoire spécifique en spécifiant le nom. Cependant, le "nom" peut en fait être n'importe quel chemin (relatif) valide sur le système, vous permettant de lire tous les fichiers auxquels le service Web a accès, pas seulement les images. Abuser de cet exemple simple est appelé "traversée de chemin". Si votre application autorise la traversée de chemin, c'est mauvais. Un read
sans défense contre pour une traversée de chemin peut être appelé mal.
Cependant, dans d'autres cas, la chaîne de read
est entièrement sous le contrôle des programmeurs (peut-être codée en dur?). Dans ce cas, il n'est pas mauvais d'utiliser read
.
Maintenant, un autre exemple simple, utilisant eval
.
Quelque part dans votre application Web, vous voulez du contenu dynamique. Vous allez permettre aux administrateurs de saisir du code exécutable. Étant donné que les administrateurs sont des utilisateurs de confiance, cela peut théoriquement être acceptable. Assurez-vous simplement de ne pas exécuter le code soumis par des non-administrateurs, et tout va bien.
(Autrement dit, jusqu'à ce que vous renvoyiez cet administrateur de Nice mais que vous ayez oublié de révoquer son accès. Maintenant, votre application Web est mise à la poubelle).
Je pense qu'un autre aspect important est la facilité avec laquelle il est possible de vérifier les entrées des utilisateurs.
Vous utilisez l'entrée utilisateur dans un appel de lecture? Assurez-vous (très) simplement que l'entrée de l'appel en lecture ne contient rien de malveillant. Normalisez le chemin d'accès et vérifiez que le fichier ouvert se trouve dans votre répertoire multimédia. Maintenant, c'est sûr.
Entrée utilisateur lors d'un appel en écriture? Même!
Injection SQL? Échappez-vous simplement ou utilisez des requêtes paramétrées et vous êtes en sécurité.
Eval? Comment allez-vous vérifier l'entrée utilisée pour l'appel eval
? Vous pouvez travailler très dur, mais c'est vraiment très difficile (sinon impossible) de le faire fonctionner en toute sécurité.
Désormais, chaque fois que vous utilisez la saisie utilisateur, vous devez peser les avantages de son utilisation par rapport aux dangers. Protégez son utilisation autant que possible.
Considérez à nouveau la substance eval
able dans l'exemple d'administration. Je vous ai dit que c'était en quelque sorte ok.
Maintenant, considérez qu'il y a en fait un endroit dans votre application Web où vous avez oublié d'échapper au contenu utilisateur (HTML, XSS). C'est une infraction moindre que l'évaluation accessible à l'utilisateur. Mais, en utilisant le contenu utilisateur non échappé, un utilisateur peut reprendre le navigateur Web d'un administrateur et ajouter un blob eval
able via la session admins, permettant à nouveau un accès complet au système.
(La même attaque à plusieurs étapes peut être effectuée avec une injection SQL au lieu de XSS, ou certaines écritures de fichiers arbitraires remplaçant le code exécutable au lieu d'utiliser eval
)
Pour que cette fonctionnalité fonctionne, cela signifie que je dois garder une couche de réflexion autour qui permet un accès complet à l'état interne du programme entier.
Pour les langages interprétés, je peux simplement utiliser l'état de l'interpréteur, ce qui est facile, mais en combinaison avec les compilateurs JIT, il augmente encore considérablement la complexité.
Sans eval
, le compilateur JIT peut souvent prouver que les données locales d'un thread ne sont pas accessibles à partir d'un autre code, il est donc parfaitement acceptable de réorganiser les accès, d'omettre les verrous et de mettre en cache les données souvent utilisées pendant des périodes plus longues. Lorsqu'un autre thread exécute une instruction eval
, il peut être nécessaire de synchroniser le code compilé JIT en cours d'exécution avec cela, de sorte que le code généré JIT a soudainement besoin d'un mécanisme de secours qui revient à une exécution non optimisée dans un délai raisonnable.
Ce type de code a tendance à avoir beaucoup de bogues subtils, difficiles à reproduire, et en même temps, il limite également l'optimisation dans le compilateur JIT.
Pour les langues compilées, le compromis est encore pire: la plupart des optimisations sont interdites, et j'ai besoin de garder des informations détaillées sur les symboles et un interprète, donc la flexibilité supplémentaire ne vaut généralement pas la peine - il est souvent plus facile de définir une interface pour certains internes structures, p.ex. en donnant un script vue et contrôleur accès simultané au programme modèle.
Je rejette la prémisse que eval
est considéré plus mauvais que l'arithmétique du pointeur ou plus dangereux que l'accès direct à la mémoire et au système de fichiers. Je ne connais aucun développeur sensé qui pourrait croire cela. En outre, les langues prenant en charge l'arithmétique des pointeurs/l'accès direct à la mémoire ne prennent généralement pas en charge eval
et vice versa, donc je suis sûr de la fréquence à laquelle une telle comparaison serait même pertinente.
Mais eval
pourrait être une vulnérabilité plus bien connue, pour la simple raison qu'elle est prise en charge par JavaScript. JavaScript est un langage en bac à sable sans accès direct à la mémoire ou au système de fichiers, il n'a donc tout simplement pas ces vulnérabilités sauf les faiblesses dans l'implémentation du langage lui-même. Eval
est donc l'une des fonctionnalités les plus dangereuses du langage, car elle ouvre la possibilité d'une exécution arbitraire de code. Je crois que beaucoup plus de développeurs développent en JavaScript qu'en C/C++, donc eval
est simplement plus important à prendre en compte que les débordements de tampon pour la majorité des développeurs.
Aucun programmeur sérieux ne considérerait Eval comme "mal". C'est simplement un outil de programmation, comme les autres. La peur (s'il y a une peur) de cette fonction n'a rien à voir avec la culture populaire. Il s'agit simplement d'une commande dangereuse qui est souvent utilisée à mauvais escient et peut introduire de graves failles de sécurité et dégrader les performances. D'après ma propre expérience, je dirais qu'il est rare de rencontrer un problème de programmation qui ne peut être résolu de manière plus sûre et efficace par d'autres moyens. Les programmeurs les plus susceptibles d'utiliser eval sont ceux qui sont probablement les moins qualifiés pour le faire en toute sécurité.
Cela dit, il existe des langues où l'utilisation d'eval est appropriée. Perl me vient à l'esprit. Cependant, je trouve personnellement qu'il est très rarement nécessaire dans d'autres langages plus modernes, qui prennent en charge nativement la gestion structurée des exceptions.
Je pense que vous le couvrez assez bien dans la partie suivante de votre question (je souligne):
ils ont une bonne utilisation, tant qu'ils sont utilisés correctement
Pour les 95% qui pourraient l'utiliser correctement, tout va bien; mais il y aura toujours des gens qui ne l'utiliseront pas correctement. Certains d'entre eux seront dus à l'inexpérience et au manque de capacités, le reste sera malveillant.
Il y aura toujours des gens qui voudront repousser les limites et trouver des failles de sécurité - certaines pour le bien, d'autres pour le mal.
Quant à l'aspect historique de celui-ci, les fonctions de type eval
permettent essentiellement Exécution de code arbitraire qui a été précédemment exploitée dans le CMS web populaire, Joomla! . Avec Joomla! Alimentant plus de 2,5 millions de sites dans le monde , cela représente beaucoup de dommages potentiels non seulement pour les visiteurs de ces sites, mais potentiellement pour l'infrastructure sur laquelle ils sont hébergés et également pour la réputation des sites/entreprises qui ont été exploité.
Le Joomla! l'exemple peut être simple, mais c'est un qui a été enregistré.
Il existe de bonnes raisons de décourager l'utilisation de eval
(bien que certaines soient spécifiques à certaines langues).
eval
est souvent surprenant (c'est-à-dire que vous pensez que eval(something here)
devrait faire une chose, mais il en fait une autre, déclenchant éventuellement des exceptions)Personnellement, je n'irais pas jusqu'à dire que c'est du mal, mais je contesterai toujours l'utilisation de eval
dans une revue de code, avec des libellés du type "avez-vous envisagé du code ici? " (si j'ai le temps de faire au moins un remplacement provisoire), ou "êtes-vous sûr qu'un eval est vraiment la meilleure solution ici?" (si je ne le fais pas).
Dans la société Minsky Society of Mind , chapitre 6.4, dit-il
Il y a un moyen pour un esprit de se surveiller et de garder une trace de ce qui se passe. Divisez le cerveau en deux parties, A et B. Connectez les entrées et sorties du cerveau A au monde réel - afin qu'il puisse détecter ce qui s'y passe. Mais ne connectez pas du tout le cerveau B au monde extérieur; au lieu de cela, connectez-le pour que le cerveau A soit le monde du cerveau B!
Lorsqu'un programme (B) écrit un autre programme (A), il fonctionne comme un méta-programme. Le sujet de B est le programme A.
Il existe un programme C. C'est celui dans votre tête qui écrit le programme B.
Le danger est qu'il puisse y avoir quelqu'un (C ') avec une mauvaise intention, qui peut interférer dans ce processus, donc vous obtenez des choses comme l'injection SQL.
Le défi consiste à rendre le logiciel plus intelligent sans le rendre également plus dangereux.
Vous avez beaucoup de bonnes réponses ici et la raison principale est clairement que l'exécution de code arbitraire est mauvaise mmmkay mais j'ajouterai un autre facteur que d'autres n'ont touché qu'au bord de:
Il est vraiment difficile de dépanner le code qui est évalué à partir d'une chaîne de texte. Vos outils de débogage normaux sont à peu près directement sortis de la fenêtre et vous êtes réduit à un suivi ou un écho très old-school. Évidemment, cela n'a pas tellement d'importance si vous avez un fragment dans une seule ligne que vous voulez eval
mais en tant que programmeur expérimenté, vous pouvez probablement trouver une meilleure solution pour cela, tandis que la génération de code à plus grande échelle peut être quelque part où une fonction d'évaluation devient un allié potentiellement utile.