IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Contribuez Python Discussion :

Une fonction "eval" sécurisée


Sujet :

Contribuez Python

  1. #1
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 481
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut Une fonction "eval" sécurisée
    Voilà longtemps que j'utilise la fonction "eval" dans mes programmes pour calculer des expressions mathématiques, et je me suis aperçu avec le temps qu'elle avait de nombreux trous de sécurité. C'est à dire que si l'expression à évaluer était écrite par un utilisateur quelconque, elle pouvait contenir des "instructions vicieuses" susceptibles de faire autre chose que du calcul, y compris de nuire au système qu'on utilise...

    Pour des raisons évidentes, je ne vous donnerai pas ici beaucoup d'instructions vicieuses possibles, mais je peux vous en donner un exemple qui montre le problème:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    print(eval('__import__("sys").platform'))
    win32
    (je suis sous Windows)

    Vous voyez qu'on peut importer un module, et lui faire exécuter des instructions système qui n'ont rien à voir avec un calcul d'expression mathématique!

    Il reste qu'on peut faire aussi quelque chose d'utile pour un calcul, par exemple si on veut calculer le sinus de 5 sans avoir importé le module math:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    print(eval('__import__("math").sin(5)'))
    -0.9589242746631385
    Pour se protéger le mieux possible, on va tester à l'aide d'expressions régulières pour interdire certaines instructions dangereuses. Voilà la fonction que j'utilise:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    import re
     
    class EvalSecur:
        """Evalue une expression après avoir vérifié sa sécurité
           On peut, en plus, limiter les 2 dictionnaires varglob et varloc
             Si nécessaire, faire varglob = {'__builtins__':{}}
        """
     
        #=========================================================================
        def __init__(self):
     
            # recherche des noms de type "système" comme "__xxxxx__"
            self.regsys = re.compile(r"(.*\W)?__\w*__(\W.*)?")
     
            # recherche de la fonction "eval(...)"
            self.regeval = re.compile(r"(.*\W)?(eval *\()(\W.*)?")
     
            # recherche de la fonction "exec(...)"
            self.regexec = re.compile(r"(.*\W)?(exec *\()(\W.*)?")
     
            # recherche de lambda
            self.reglambda = re.compile(r"(.*\W)?(lambda )(\W.*)?")
     
            # recherche de os
            self.regos = re.compile(r"(.*\W)?(os\.)(\W.*)?")
     
            # recherche de io
            self.regos = re.compile(r"(.*\W)?(io\.)(\W.*)?")
     
            # recherche de sys
            self.regos = re.compile(r"(.*\W)?(sys\.)(\W.*)?")
     
            # recherche de "open"
            self.regopen = re.compile(r"(.*\W)?(open *\()(\W.*)?")
     
            # recherche de "import"
            self.regimport = re.compile(r"(.*\W)?(import)(\W.*)?")
     
        #=========================================================================
        def __call__(self, expression, varglob={}, varloc={}):
     
            # interdit les noms système de type __xxx__
            if self.regsys.search(expression) is not None:
                raise ValueError ("nom système de type __xxx__ interdit")
     
            # interdit eval
            if self.regeval.search(expression) is not None:
                raise ValueError ("eval interdit")
     
            # interdit exec
            if self.regexec.search(expression) is not None:
                raise ValueError ("exec interdit")
     
            # interdit lambda
            if self.reglambda.search(expression) is not None:
                raise ValueError ("lambda interdit")
     
            # interdit "os."
            if self.regos.search(expression) is not None:
                raise ValueError ("os interdit")
     
            # interdit "io."
            if self.regos.search(expression) is not None:
                raise ValueError ("io interdit")
     
            # interdit "sys."
            if self.regos.search(expression) is not None:
                raise ValueError ("sys interdit")
     
            # interdit "open("
            if self.regopen.search(expression) is not None:
                raise ValueError ("open interdit")
     
            # interdit "import"
            if self.regimport.search(expression) is not None:
                raise ValueError ("import interdit")
     
            try:
                return eval(expression, varglob, varloc)
            except Exception as msgerr:
                raise ValueError (msgerr.args[0])
     
    eval2 = EvalSecur()
    Si on calcule l'instruction système utilisée plus haut avec cette nouvelle fonction, voilà ce que ça donne:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    print(eval2('__import__("sys").platform'))
    ValueError: nom système de type __xxx__ interdit
    Bien sûr, cela ralentit un peu le calcul en contrepartie de sa sécurité, et il faut bien voir si une telle fonction est utile dans le programme qu'on est en train de développer:

    - Si la fonction eval est utilisée pour calculer des expressions qui n'ont pas été écrites par un utilisateur, on n'en a pas besoin.

    - Et si un utilisateur a un accès direct au PC avec les droits administrateur, on n'en a pas besoin non plus puisqu'il peut faire n'importe quoi par ailleurs pour nuire au système en dehors de cette fonction eval!

    Par contre, si la fonction eval est utilisée dans un programme accessible au réseau public, ce genre de précaution est indispensable!

    Enfin, je ne prétends pas avoir couvert toutes les "expressions vicieuses" possibles! Et si vous en avez qui passent mes interdictions, je serais ravi de les connaître! De préférence par mp pour ne donner de mauvaises idées à personne...

    Amusez-vous bien!
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 379
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 379
    Points : 36 923
    Points
    36 923
    Par défaut
    Salut,

    Citation Envoyé par tyrtamos Voir le message
    Par contre, si la fonction eval est utilisée dans un programme accessible au réseau public, ce genre de précaution est indispensable!
    Même ast.literal_eval serait insuffisant dans ce cas: on évite, on s'interdit l'exécution de données quelconques.
    note: ce qui n'interdit pas d'utiliser eval dans des exercices pédagogiques ou dans les codes qu'on écrit pour nous mêmes. Et inutile de sécuriser quoi que ce soit dans ce cas là.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 481
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour wiztricks

    C'est tout de même curieux qu'un eval qui ne fasse que du calcul mathématique n'existe pas en Python. Je me rappelle avoir programmé (il y a longtemps...) une telle fonction en Pascal, et ça marchait très bien. Et comme c'était compilé, c'était rapide.

    Y aurait-il possibilité d'appeler une telle fonction issue de la bibliothèque C ?
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  4. #4
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 379
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 379
    Points : 36 923
    Points
    36 923
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    Y aurait-il possibilité d'appeler une telle fonction issue de la bibliothèque C ?
    Avec Python on peut utiliser ast.parse pour analyser la chaine de caractères, vérifier qu'operateurs et autres sont "licites" puis évaluer l'expression. Et lorsqu'on sait ce qu'on veut, on cherche le code sur Internet pour trouver un truc comme ceci.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  5. #5
    Expert éminent
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    3 923
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 923
    Points : 7 322
    Points
    7 322
    Par défaut
    Bonjour,

    Citation Envoyé par tyrtamos
    C'est tout de même curieux qu'un eval qui ne fasse que du calcul mathématique n'existe pas en Python
    Je serait tout de même surpris qu'une telle fonction n'existe pas avec le module sympy.

    On peut aussi analyser le travail du module numexpr.
    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

  6. #6
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 379
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 379
    Points : 36 923
    Points
    36 923
    Par défaut
    Citation Envoyé par fred1599 Voir le message
    Je serait tout de même surpris qu'une telle fonction n'existe pas avec le module sympy.
    La documentation dit que cette fonction utilise eval et qu'on doit lui appliquer les mêmes restrictions.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  7. #7
    Expert éminent
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    3 923
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 923
    Points : 7 322
    Points
    7 322
    Par défaut
    Hello,

    Citation Envoyé par wiztricks
    La documentation dit que cette fonction utilise eval et qu'on doit lui appliquer les mêmes restrictions.
    Je ne connais pas sympy, mais ma proposition se rapprochait d'une méthode telle que evalf.

    Par contre n'ayant pas testé, il est possible que ça ne soit pas ce qu'il faut et j'ai pas le temps de me taper la doc, mais il est quand même surprenant qu'un module de cette importance n'est pas ce type de fonctionnalité.
    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

  8. #8
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 481
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Bonjour

    J'ai regardé un peu le module ast. J'ai vu sur plusieurs sites web que ast.literal_eval pouvait être un eval sécurisé. Mais ça ne marche pas:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    x = "2*3"
     
    z = eval(x)
    print(x, "===>", z, type(z))
    2*3 ===> 6 <class 'int'>
     
    import ast
     
    z = ast.literal_eval(x)
    print(x, "===>", z, type(z))
    ValueError: malformed node or string on line 1: <ast.BinOp object at 0x000001CBF530CF40>
    Par contre, ast.literal_eval fait très bien une chose qui m'intéresse par ailleurs: retrouver un objet Python avec son type, quand il est donné sous forme de chaine de caractères:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    x = "1"
    z = ast.literal_eval(x)
    print(x, "===>", z, type(z))
    1 ===> 1 <class 'int'>
     
    x = "[1,2,3]"
    z = ast.literal_eval(x)
    print(x, "===>", z, type(z))
    [1,2,3] ===> [1, 2, 3] <class 'list'>
    Pour trouver un eval sécurisé, j'ai plutôt regardé le module externe "asteval" sur pypi (https://pypi.org/project/asteval/). Basé sur ast, il a l'air d'échapper aux principales instructions vicieuses que eval accepte trop facilement. La doc est ici: https://lmfit.github.io/asteval/. Mais, bien sûr, personne ne peut garantir une totale sécurité!

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from asteval import Interpreter
    aeval = Interpreter()
     
    x = "2*3"
     
    y = aeval(x)
    print(x, "===>", y, type(y))
    2*3 ===> 6 <class 'int'>
     
    from math import *
     
    x = "sqrt(2)"
     
    y = aeval(x)
    print(x, "===>", y, type(y))
    sqrt(2) ===> 1.4142135623730951 <class 'numpy.float64'>
    Dans le dernier exemple, il utilise la bibliothèque numpy. Mais la conversion en float Python est facile: il suffit de faire y = float(aeval(x))

    [Edit]: des commentaires intéressants sur la sécurité de asteval ici: https://lmfit.github.io/asteval/moti...afe-is-asteval
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  9. #9
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 379
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 379
    Points : 36 923
    Points
    36 923
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    J'ai regardé un peu le module ast. J'ai vu sur plusieurs sites web que ast.literal_eval pouvait être un eval sécurisé. Mais ça ne marche pas:
    Ca marchait avec Python2 et c'est beaucoup plus restrient depuis Python3 (peut être pas les premières versions mais je ne vais pas farfouiller les releases notes pour avoir plus de précision).

    Citation Envoyé par tyrtamos Voir le message
    Pour trouver un eval sécurisé, j'ai plutôt regardé le module externe "asteval" sur pypi (https://pypi.org/project/asteval/). Basé sur ast, il a l'air d'échapper aux principales instructions vicieuses que eval accepte trop facilement.
    Un interpréteur qui interdit/limite certaines fonctionnalités en autorisant tout le reste laisse un nombre indéfini de portes ouvertes à des trous de sécurité qui ne se compare pas à l'évaluation d'expression où ne seront autorisées que... (comme dans l'exemple que j'ai proposé).
    note: intuitivement, c'est un peu comme comparer un infini non dénombrable à un infini dénombrable: on change carrément d'univers!

    Ceci dit, faire un peu le tour de l'état de l'art (ce qui a déjà été fait par d'autres) avant de se lancer à coder un truc en mode challenge est sans doute un peu frustrant (partir à coder est plus gratifiant dans l'immédiat) mais ca fait partie du boulot à faire avant de coder.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  10. #10
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 481
    Points : 9 280
    Points
    9 280
    Billets dans le blog
    6
    Par défaut
    Quand il s'agit de sécurité, on a de bonnes raisons d'avoir des doutes, mais quand on pense qu'une utilisation vicieuse de eval peut effacer le disque dur, n'importe quelle solution meilleure est bonne à prendre, en attendant qu'une solution parfaite soit trouvée (mais où est-elle?)...
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  11. #11
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 379
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 379
    Points : 36 923
    Points
    36 923
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    en attendant qu'une solution parfaite soit trouvée (mais où est-elle?)...
    Il n'y a rien de parfait, juste des compromis bénéfices/risques raisonnables.

    Si j'écris une application desktop, les dégâts qui pourront être provoqués par un utilisateur malicieux seront limités (par ses droits d'accès aux différents objets systèmes)... S'il plante son application, il pourra la redémarrer, ca ne gênera que lui. S'il détruit des fichiers, c'est qu'il avait le droit de... (et aurait pu le faire autrement).

    On n'aura pas les mêmes soucis lorsque la chaine des caractères devra être interprétée par un serveur multi usages/utilisateurs distants où les dysfonctionnements pourront affecter les différents services rendus (par le serveur).

    Le vrai boulot n'est pas côté code mais dans l'arbitrage sur quoi faire en fonction du contexte et d'arriver à tester à des coûts raisonnables que ça le fait.


    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

Discussions similaires

  1. Utilisation de double quote dans une fonction
    Par berceker united dans le forum Général JavaScript
    Réponses: 6
    Dernier message: 15/02/2021, 18h16
  2. [PHP 5.3] [POO] : faire une fonction quote
    Par gwendoline-bricout dans le forum Langage
    Réponses: 7
    Dernier message: 20/02/2014, 10h41
  3. Une fonction comme %eval
    Par FanFan44 dans le forum Macro
    Réponses: 2
    Dernier message: 07/08/2013, 19h01
  4. Existe-t-il une fonction Eval() sous Delphi ?
    Par Hell dans le forum Langage
    Réponses: 5
    Dernier message: 20/12/2004, 17h45

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo