Assurez-vous que le code dangereux n'est pas utilisé accidentellement

10

Une fonction f()utilise eval()(ou quelque chose de dangereux) avec des données que j'ai créées et stockées local_filesur la machine exécutant mon programme:

import local_file

def f(str_to_eval):
    # code....
    # .... 
    eval(str_to_eval)    
    # ....
    # ....    
    return None


a = f(local_file.some_str)

f() est sûr à exécuter car les chaînes que je lui fournis sont les miennes.

Cependant, si jamais je décidais de l'utiliser pour quelque chose de dangereux (par exemple, une entrée utilisateur), les choses pourraient mal tourner . De plus, si l' local_filearrêt cesse d'être local, cela créerait une vulnérabilité, car je devrais également faire confiance à la machine qui fournit ce fichier.

Comment dois-je m'assurer de ne jamais "oublier" que cette fonction n'est pas sûre à utiliser (sauf si des critères spécifiques sont remplis)?

Remarque: eval()est dangereux et peut généralement être remplacé par quelque chose de sûr.

Paradoxe de Fermi
la source
1
J'aime généralement accepter les questions qui sont posées plutôt que de dire "vous ne devriez pas faire cela", mais je ferai une exception dans ce cas. Je suis tout à fait certain qu'il n'y a aucune raison pour que vous utilisiez eval. Ces fonctions sont incluses avec des langages tels que PHP et Python comme une sorte de preuve de concept ce qui peut être fait avec les langages interprétés. Il est fondamentalement inconcevable que vous puissiez écrire du code qui crée du code, mais que vous ne pouvez pas coder cette même logique dans une fonction. Python a probablement des rappels et des liaisons de fonctions qui vous permettront de faire ce dont vous avez besoin
user1122069
1
" Je dois également faire confiance à la machine qui fournit ce fichier " - vous devez toujours faire confiance à la machine sur laquelle vous exécutez votre code.
Bergi
Mettez un commentaire dans le fichier disant "Cette chaîne est évaluée; veuillez ne pas mettre de code malveillant ici"?
user253751
@immibis Un commentaire ne serait pas suffisant. Je vais bien sûr avoir un énorme "AVERTISSEMENT: cette fonction utilise eval ()" dans les documents de la fonction, mais je préfère compter sur quelque chose de beaucoup plus sûr que cela. Par exemple (en raison du temps limité), je ne relis pas toujours les documents d'une fonction. Je ne pense pas non plus que je devrais l'être. Il existe des moyens plus rapides (c'est-à-dire plus efficaces) de savoir que quelque chose n'est pas sûr, comme les méthodes liées par Murphy dans sa réponse.
Paradoxe de Fermi
@Fermiparadox lorsque vous retirez l'eval de la question, cela devient sans exemple utile. Parlez-vous maintenant d'une fonction qui n'est pas sécurisée en raison d'une surveillance délibérée, comme ne pas échapper aux paramètres SQL. Voulez-vous dire que le code de test n'est pas sûr pour un environnement de production? Quoi qu'il en soit, maintenant j'ai lu votre commentaire sur la réponse d'Amon - fondamentalement, vous cherchiez comment sécuriser votre fonction.
user1122069

Réponses:

26

Définissez un type pour décorer les entrées «sûres». Dans votre fonction f(), assurez-vous que l'argument a ce type ou renvoyez une erreur. Soyez assez intelligent pour ne jamais définir de raccourci def g(x): return f(SafeInput(x)).

Exemple:

class SafeInput(object):
  def __init__(self, value):
    self._value = value

  def value(self):
    return self._value

def f(safe_string_to_eval):
  assert type(safe_string_to_eval) is SafeInput, "safe_string_to_eval must have type SafeInput, but was {}".format(type(safe_string_to_eval))
  ...
  eval(safe_string_to_eval.value())
  ...

a = f(SafeInput(local_file.some_str))

Bien que cela puisse être facilement contourné, il est plus difficile de le faire par accident. Les gens pourraient critiquer un contrôle de type aussi draconien, mais ils manqueraient le point. Étant donné que le SafeInputtype n'est qu'un type de données et non une classe orientée objet pouvant être sous-classée, la vérification de l'identité du type avec type(x) is SafeInputest plus sûre que la vérification de la compatibilité du type avec isinstance(x, SafeInput). Étant donné que nous voulons appliquer un type spécifique, ignorer le type explicite et simplement effectuer un typage de canard implicite n'est pas non plus satisfaisant ici. Python a un système de type, alors utilisons-le pour détecter d'éventuelles erreurs!

amon
la source
5
Un petit changement pourrait cependant être nécessaire. assertest supprimé automatiquement car j'utiliserai du code python optimisé. Il if ... : raise NotSafeInputErrorfaudrait quelque chose comme .
Paradoxe de Fermi
4
Si vous suivez cette voie de toute façon, je vous recommande fortement de passer à la eval()méthode SafeInput, afin de ne pas avoir besoin d'une vérification de type explicite. Donnez à la méthode un nom qui n'est utilisé dans aucune de vos autres classes. De cette façon, vous pouvez toujours vous moquer du tout à des fins de test.
Kevin
@Kevin: Étant donné qu'il evala accès aux variables locales, il se peut que le code dépende de la façon dont il mute les variables. En déplaçant l'eval dans une autre méthode, il n'aura plus la possibilité de muter les variables locales.
Sjoerd Job Postmus
Une autre étape pourrait être de demander au SafeInputconstructeur de prendre un nom / handle du fichier local et de faire la lecture lui-même, en s'assurant que les données proviennent bien d'un fichier local.
Owen
1
Je n'ai pas beaucoup d'expérience avec python, mais n'est-il pas préférable de lever une exception? Dans la plupart des langues, les assertions peuvent être désactivées et sont (si je comprends bien) plus pour le débogage que pour la sécurité. Les exceptions et la fermeture de la console garantissent que le code suivant ne sera pas exécuté.
user1122069
11

Comme Joel l'a souligné une fois, faites en sorte que le mauvais code semble incorrect ( faites défiler jusqu'à la section "La vraie solution", ou mieux encore lisez-le complètement; cela en vaut la peine, même s'il s'adresse à des variables au lieu de noms de fonctions). Je suggère donc humblement de renommer votre fonction en quelque chose comme f_unsafe_for_external_use()- vous trouverez très probablement un schéma d'abréviation vous-même.

Edit: Je pense que la suggestion d'Amon est une très bonne variation basée sur OO sur le même thème :-)

Murphy
la source
0

En complément de la suggestion de @amon, vous pouvez également penser à combiner le modèle de stratégie ici, ainsi que le modèle d'objet de méthode.

class AbstractFoobarizer(object):
    def handle_cond(self, foo, bar):
        """
        Handle the event or something.
        """
        raise NotImplementedError

    def run(self):
        # do some magic
        pass

Et puis une implémentation «normale»

class PrintingFoobarizer(AbstractFoobarizer):
    def handle_cond(self, foo, bar):
        print("Something went very well regarding {} and {}".format(foo, bar))

Et une implémentation admin-input-eval

class AdminControllableFoobarizer(AbstractFoobarizer):
    def handle_cond(self, foo, bar):
        # Look up code in database/config file/...
        code = get_code_from_settings()
        eval(code)

(Remarque: avec un exemple réel, je pourrais peut-être donner un meilleur exemple).

Sjoerd Job Postmus
la source