Asegúrese de que el código inseguro no se use accidentalmente

10

Una función f()usa eval()(o algo tan peligroso) con los datos que creé y almacené en local_filela máquina que ejecuta mi programa:

import local_file

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


a = f(local_file.some_str)

f() es seguro de ejecutar ya que las cadenas que le proporciono son mías.

Sin embargo, si alguna vez decido usarlo para algo inseguro (por ejemplo, entrada del usuario), las cosas podrían salir terriblemente mal . Además, si local_filedeja de ser local, crearía una vulnerabilidad, ya que también necesitaría confiar en la máquina que proporciona ese archivo.

¿Cómo debo asegurarme de nunca "olvidar" que esta función no es segura de usar (a menos que se cumplan criterios específicos)?

Nota: eval()es peligroso y generalmente puede ser reemplazado por algo seguro.

Paradoja de Fermi
fuente
1
Por lo general, me gusta seguir las preguntas que se hacen en lugar de decir "no debes hacer esto", pero haré una excepción en este caso. Estoy bastante seguro de que no hay ninguna razón por la que deba usar eval. Estas funciones se incluyen con lenguajes como PHP y Python como una especie de prueba de concepto de cuánto se puede hacer con lenguajes interpretados. Es fundamentalmente inconcebible que pueda escribir código que cree código, pero que no pueda codificar esa misma lógica en una función. Python es probable que tenga devoluciones de llamada y los enlaces de función que le permitirá hacer lo que tiene con lo que
user1122069
1
" También necesitaría confiar en la máquina que proporciona ese archivo ", siempre debe confiar en la máquina en la que está ejecutando su código.
Bergi
¿Ponga un comentario en el archivo que diga "Esta cadena se evalúa; por favor no ponga código malicioso aquí"?
user253751
@immibis Un comentario no sería suficiente. Por supuesto, tendré un enorme "ADVERTENCIA: esta función usa eval ()" en los documentos de la función, pero preferiría confiar en algo mucho más seguro que eso. Por ejemplo (debido al tiempo limitado) no siempre vuelvo a leer los documentos de una función. Tampoco creo que debería estarlo. Hay formas más rápidas (es decir, más eficientes) de saber que algo no es seguro, como los métodos vinculados por Murphy en su respuesta.
Fermi paradoja
@Fermiparadox cuando elimina la evaluación de la pregunta, se convierte sin un ejemplo útil. ¿Está hablando ahora de una función que es insegura debido a la supervisión deliberada, como no escapar de los parámetros de SQL? ¿Quiere decir que el código de prueba no es seguro para un entorno de producción? De todos modos, ahora leí tu comentario sobre la respuesta de Amon, básicamente estabas buscando cómo asegurar tu función.
user1122069

Respuestas:

26

Defina un tipo para decorar entradas "seguras". En su función f(), afirme que el argumento tiene este tipo o arroje un error. Sé lo suficientemente inteligente como para nunca definir un atajo def g(x): return f(SafeInput(x)).

Ejemplo:

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))

Si bien esto puede evitarse fácilmente, hace que sea más difícil hacerlo por accidente. La gente podría criticar una verificación de tipo tan draconiana, pero se perderían el punto. Dado que el SafeInputtipo es solo un tipo de datos y no una clase orientada a objetos que podría subclasificarse, verificar la identidad del tipo type(x) is SafeInputes más seguro que verificar la compatibilidad del tipo isinstance(x, SafeInput). Dado que queremos imponer un tipo específico, ignorar el tipo explícito y simplemente hacer un tipeo de pato implícito tampoco es satisfactorio aquí. Python tiene un sistema de tipos, ¡así que usémoslo para detectar posibles errores!

amon
fuente
55
Sin embargo, podría ser necesario un pequeño cambio. assertse elimina automáticamente ya que utilizaré el código optimizado de Python. Algo así if ... : raise NotSafeInputErrorsería necesario.
Fermi paradoja
44
Si de todos modos va por este camino, le recomiendo encarecidamente que active eval()un método SafeInput, para que no necesite una verificación de tipo explícita. Déle al método un nombre que no se use en ninguna de sus otras clases. De esa manera, todavía puedes burlarte de todo para propósitos de prueba.
Kevin
@Kevin: Dado que evaltiene acceso a las variables locales, es posible que el código dependa de cómo muta las variables. Al mover la evaluación a otro método, ya no tendrá la capacidad de mutar variables locales.
Sjoerd Job Postmus
Un paso adicional podría ser que el SafeInputconstructor tome un nombre / identificador del archivo local y haga la lectura misma, asegurando que los datos realmente provengan de un archivo local.
Owen
1
No tengo mucha experiencia con Python, pero ¿no es mejor lanzar una excepción? En la mayoría de los idiomas, las afirmaciones se pueden desactivar y son (según tengo entendido) más para la depuración que por seguridad. Las excepciones y salidas a la consola garantizan que no se ejecutará el siguiente código.
user1122069
11

Como Joel señaló una vez, haga que el código incorrecto parezca incorrecto (desplácese hacia abajo hasta la sección "La solución real", o mejor aún, léalo por completo; vale la pena, incluso si se trata de nombres de variables en lugar de funciones). Así que humildemente sugiero cambiar el nombre de su función a algo como f_unsafe_for_external_use(): lo más probable es que tenga un esquema de abreviatura usted mismo.

Editar: creo que la sugerencia de amon es una muy buena variación basada en OO sobre el mismo tema :-)

Murphy
fuente
0

Complementando la sugerencia de @amon, también puede pensar en combinar el patrón de estrategia aquí, así como el patrón de objeto del método.

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

    def run(self):
        # do some magic
        pass

Y luego una implementación 'normal'

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

Y una implementación 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)

(Nota: con un ejemplo del mundo real, podría dar un mejor ejemplo).

Sjoerd Job Postmus
fuente