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:
(je suis sous Windows)
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 print(eval('__import__("sys").platform')) win32
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:
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 print(eval('__import__("math").sin(5)')) -0.9589242746631385
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
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()
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:
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
- 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!
Partager