¿Por qué las características tipo evaluación son consideradas como malas, en contraste con otras características posiblemente dañinas?

51

La mayoría de los lenguajes modernos (que se interpretan de alguna manera) tienen algún tipo de función eval . Dicha función ejecuta un código de idioma arbitrario, la mayoría de las veces se pasa como el argumento principal como una cadena (diferentes idiomas pueden agregar más funciones a la función eval).

Entiendo que no se debe permitir que los usuarios ejecuten esta función ( editar , es decir, que se ingrese directa o indirectamente una entrada arbitraria de un usuario arbitrario a eval ), especialmente con el software del lado del servidor. Ya que podrían forzar el proceso para ejecutar código malicioso. De esa manera, los tutoriales y las comunidades nos dicen que no usemos eval. Sin embargo, hay muchas veces en las que eval es útil y se usa:

  • Reglas de acceso personalizadas a elementos de software (IIRC OpenERP tiene un objeto ir.rule que puede usar código dinámico de Python).
  • Cálculos y / o criterios personalizados (OpenERP tiene campos como ese para permitir cálculos de códigos personalizados).
  • Analizadores de informes de OpenERP (sí, sé que te estoy volviendo loco con las cosas de OpenERP ... pero es el principal ejemplo que tengo).
  • Codificación de los efectos de hechizos en algunos juegos de rol.

Por lo tanto, tienen un buen uso, siempre y cuando se utilicen correctamente. La principal ventaja es que la función permite a los administradores escribir código personalizado sin tener que crear más archivos e incluirlos (aunque la mayoría de los marcos que usan funciones eval tienen también una forma de especificar un archivo, módulo, paquete, ... para leer).

Sin embargo, eval es malvado en la cultura popular. Se me ocurren cosas como entrar en tu sistema.

Sin embargo, hay otras funciones que podrían ser perjudiciales si los usuarios accedieran de alguna manera: desvincular, leer, escribir (semántica de archivos), asignación de memoria y aritmética de punteros, acceso a modelos de base de datos (incluso si no se consideran los casos inyectables de SQL). / p>

Básicamente, la mayoría del tiempo cuando cualquier código no se escribe correctamente o no se ve correctamente (recursos, usuarios, entornos, ...), el código es malo y puede llevar incluso a un impacto económico.

Pero hay algo especial con las funciones eval (independientemente del idioma).

Pregunta : ¿Existe algún hecho histórico para que este temor se convierta en parte de la cultura popular, en lugar de prestar la misma atención a las otras características posiblemente peligrosas?

    
pregunta Luis Masuelli 01.03.2016 - 17:56

13 respuestas

76

Una función eval por sí misma no es mala, y hay un punto sutil que no creo que estés haciendo:

Permitir que un programa ejecute una entrada de usuario arbitraria es malo

Escribí un código que usaba un tipo de función eval y era seguro: el programa y los parámetros estaban codificados. A veces, no hay una función de biblioteca o lenguaje para hacer lo que necesita el programa y ejecutar un comando de shell es la ruta corta. "Tengo que terminar de codificar esto en unas pocas horas, pero escribir Java / .NET / PHP / cualquier código tomará dos días. O puedo eval en cinco minutos".

Una vez que permita que los usuarios ejecuten lo que quieran, incluso si está bloqueado por el privilegio del usuario o detrás de una pantalla "segura", creará vectores de ataque. Cada semana, algunos CMS aleatorios, software de blogs, etc. tienen un agujero de seguridad parcheado donde un atacante puede explotar un agujero como este. Confía en toda la pila de software para proteger el acceso a una función que se puede usar para rm -rf / o algo más catastrófico (nota: es poco probable que este comando tenga éxito, pero fallará después causando un poco de daños).

  

¿Existe algún hecho histórico para que este miedo se convierta en parte de la   cultura popular, en lugar de poner la misma atención a los demás   ¿Posibles características peligrosas?

Sí, hay un precedente histórico. Debido a los numerosos errores que se han solucionado a lo largo de los años en varios programas que permiten a los atacantes remotos ejecutar código arbitrario, la idea de eval ha caído en desgracia. Los idiomas modernos y las bibliotecas tienen una gran cantidad de funcionalidades que hacen que eval sea menos importante, y esto no es un accidente. Hace que las funciones sean más fáciles de usar y reduce el riesgo de una explotación.

Se ha prestado mucha atención a muchas características potencialmente inseguras en los idiomas populares. Si uno recibe más atención es principalmente una cuestión de opinión, pero las características eval ciertamente tienen un problema de seguridad demostrable que es fácil de entender. Por un lado, permiten ejecutar comandos del sistema operativo, incluidos los programas integrados de shell y los programas externos que son estándar (por ejemplo, rm o del ). Dos, combinado con otras vulnerabilidades, un atacante puede cargar su propio ejecutable o script de shell y luego ejecutarlo a través de su software, abriendo la puerta para que ocurra casi cualquier cosa (ninguna de ellas es buena).

Este es un problema difícil. El software es complejo, y una pila de software (por ejemplo, LAMP ) es múltiple . de software que interactúan entre sí de maneras complejas. Tenga cuidado con el uso de funciones de lenguaje como esta, y nunca permita que los usuarios ejecuten comandos arbitrarios.

    
respondido por el user22815 01.03.2016 - 18:17
38

Parte de esto es simplemente que los matices son difíciles. Es fácil decir cosas como nunca usar goto, campos públicos, interpolación de cadenas para consultas de SQL, o eval. Estas declaraciones no deben entenderse realmente como decir que nunca hay, bajo ninguna circunstancia, una razón para usarlas. Pero evitarlos como regla general es una buena idea.

Eval está muy desanimado porque combina varios problemas comunes.

En primer lugar, es susceptible a ataques de inyección. Aquí es como la inyección SQL, ya que cuando los datos controlados por el usuario se insertan en el código, es fácil que se pueda insertar accidentalmente un código arbitrario.

En segundo lugar, los principiantes tienden a usar eval para sortear código mal estructurado. Un codificador para principiantes podría escribir código que parezca:

x0 = "hello"
x1 = "world"
x2 = "how"
x3 = "are"
x4 = "you?"
for index in range(5):
   print eval("x" + index)

Esto funciona, pero realmente es la forma incorrecta de resolver este problema. Obviamente, usar una lista sería mucho mejor.

En tercer lugar, eval es típicamente ineficiente. Se gasta mucho esfuerzo en acelerar nuestras implementaciones de lenguaje de programación. Pero evaluar es difícil de acelerar y su uso por lo general tendrá efectos perjudiciales en su rendimiento.

Entonces, eval no es malo. Podríamos decir que evaluar es malo, porque bueno, es una manera pegadiza de decirlo. Cualquier programador principiante debe permanecer estrictamente alejado de eval porque lo que sea que quieran hacer, eval es, seguramente, la solución incorrecta. Para ciertos casos de uso avanzados, eval tiene sentido, y usted debe usarlo, pero obviamente tenga cuidado con las trampas.

    
respondido por el Winston Ewert 01.03.2016 - 18:19
20

Todo se reduce a que la "ejecución de código arbitrario" es un tema técnico para "poder hacer cualquier cosa". Si alguien es capaz de explotar la ejecución de código arbitrario en su código, esta es literalmente la peor vulnerabilidad de seguridad posible, ya que significa que pueden hacer todo lo posible para que lo haga su sistema.

"Otros errores posiblemente dañinos" pueden tener límites, lo que significa que son, por definición, capaces de causar menos daño que la explotación de un código arbitrario.

    
respondido por el Mason Wheeler 01.03.2016 - 18:11
15

Hay una razón práctica y teórica.

La razón práctica es que observamos que con frecuencia causa problemas. Es raro que eval resulte en una buena solución y, a menudo, resulta en una mala solución donde al final tendría una mejor si hubiera fingido que la evaluación no existía y abordara el problema de manera diferente. Entonces, el consejo simplificado es ignorarlo, y si se le ocurre un caso en el que quiera ignorar el consejo simplificado, esperemos que lo haya analizado lo suficiente como para comprender por qué el consejo simplificado no es aplicable y lo común. las trampas no te afectarán.

La razón más teórica es que si es difícil escribir un buen código, es aún más difícil escribir código que escriba un buen código. Ya sea que esté utilizando eval o generando sentencias de SQL uniendo cadenas o escribiendo un compilador JIT, lo que intenta es a menudo más difícil de lo que espera. El potencial de inyección de código malicioso es una gran parte del problema, pero aparte de eso, en general es más difícil saber si su código es correcto si su código no existe hasta el tiempo de ejecución. Por lo tanto, el consejo simplificado es mantener las cosas más fáciles para usted mismo: "usar consultas SQL parametrizadas", "no usar eval".

Tomando el ejemplo de los efectos de hechizos: una cosa es crear un compilador o intérprete Lua (o lo que sea) en tu juego para permitir a los diseñadores de juegos un lenguaje más fácil que C ++ (o lo que sea) para describir los efectos de hechizos. La mayoría de los "problemas de eval" no se aplican si todo lo que estás haciendo es evaluar el código que se ha escrito y probado e incluido en el juego o en el DLC o lo que sea que tengas. Eso es solo mezclar idiomas. Los grandes problemas te golpean cuando intentas generar Lua (o C ++, SQL, o comandos de shell, o lo que sea) sobre la marcha, y desordenar.

    
respondido por el Steve Jessop 01.03.2016 - 18:49
11

No, no hay un hecho histórico obvio.

Los males de eval son fáciles de ver desde el principio. Otras características son ligeramente peligrosas. La gente puede borrar datos. La gente puede ver datos que no deberían. Las personas pueden escribir datos que no deberían. Y solo pueden hacer la mayoría de esas cosas si de alguna manera cagas y no validas la entrada del usuario.

Con eval, pueden piratear el pentágono y hacer que parezca que lo hiciste. Pueden inspeccionar sus pulsaciones para obtener sus contraseñas. Asumiendo un lenguaje completo de Turing, pueden, literalmente, hacer cualquier cosa que su computadora sea capaz de hacer.

Y no se puede validar la entrada. Es una cadena de forma libre arbitraria. La única forma de validarlo sería crear un analizador y un motor de análisis de código para el lenguaje en cuestión. Mucha suerte con eso.

    
respondido por el Telastyn 01.03.2016 - 18:07
6

Creo que se reduce a los siguientes aspectos:

  • Necesidad
  • Uso (protegido)
  • acceso
  • Verificabilidad
  • Ataques en varias etapas

Necesidad

  

Hola, he escrito esta genial herramienta de edición de imágenes (disponible por $ 0.02). Una vez que haya abierto la imagen, puede pasar una multitud de filtros sobre su imagen. Incluso puedes crear secuencias de comandos con Python (el programa en el que escribí la aplicación). Solo usaré eval en su entrada, confiando en que usted sea un usuario respetable.

     

(más tarde)

     

Gracias por comprarlo. Como puedes ver funciona exactamente como lo prometí. Oh, ¿quieres abrir una imagen? No, no puedes. No usaré el método read ya que es algo inseguro. ¿Ahorro? No, no usaré write .

Lo que trato de decir es: necesitas leer / escribir para casi las herramientas básicas. Lo mismo para almacenar las puntuaciones más altas de tu juego tan increíble.

Sin lectura / escritura, su editor de imágenes es inútil. ¿Sin eval ? ¡Te escribiré un complemento personalizado para eso!

Uso (protegido)

Muchos métodos pueden ser potencialmente peligrosos. Como, por ejemplo, read y write . Un ejemplo común es un servicio web que le permite leer imágenes de un directorio específico especificando el nombre. Sin embargo, el 'nombre' puede ser de hecho cualquier ruta válida (relativa) en el sistema, lo que le permite leer todos los archivos a los que tiene acceso el servicio web, no solo las imágenes. Abusar de este ejemplo simple se llama 'recorrido de la ruta'. Si tu aplicación te permite atravesar el camino, es malo. Un read sin defenderse en contra de la trayectoria del camino también puede llamarse malvado.

Sin embargo, en otros casos, la cadena para read está totalmente bajo el control de los programadores (¿tal vez con código?). En ese caso, no es malo usar read .

Acceso

Ahora, otro ejemplo simple, usando eval .

En algún lugar de tu aplicación web, quieres contenido dinámico. Vas a permitir que los administradores ingresen algún código que sea ejecutable. Al ver que los administradores son usuarios de confianza, en teoría esto puede estar bien. Solo asegúrese de no ejecutar el código enviado por personas que no sean administradores, y estará bien.

(Es decir, hasta que despidió a ese agradable administrador pero olvidó revocar su acceso. Ahora su aplicación web está en la papelera).

Verificabilidad.

Otro aspecto que es importante, creo, es lo fácil que es verificar las opiniones de los usuarios.

¿Usando la entrada del usuario en una llamada de lectura? Solo asegúrese (muy) de que la entrada para la llamada de lectura no contenga nada malicioso. Normalice la ruta y verifique que el archivo que se abre esté en su directorio de medios. Ahora eso es seguro.

¿Entrada del usuario en una llamada de escritura? ¡Mismo!

¿Inyección SQL? Simplemente escápalo o usa consultas parametrizadas y estarás seguro.

Eval? ¿Cómo va a verificar la entrada que se usa para la llamada eval ? Puedes trabajar muy duro, pero es realmente difícil (si no imposible) hacer que funcione de manera segura.

Ataques en varias etapas

Ahora, cada vez que use la entrada del usuario, debe sopesar los beneficios de usarla, en contra de los peligros. Protege su uso tanto como puedas.

Considera de nuevo las cosas eval able en el ejemplo de administrador. Te dije que estaba bastante bien.

Ahora, considere que en realidad hay un lugar en su aplicación web donde olvidó escapar del contenido del usuario (HTML, XSS). Eso es una ofensa menor que la evaluación accesible por el usuario. Sin embargo, al utilizar el contenido del usuario sin escaparse, un usuario puede tomar el control del navegador web de un administrador y agregar un blob eval capaz a través de la sesión de administradores, lo que permite nuevamente el acceso completo al sistema.

(Se puede realizar el mismo ataque en varias etapas con la inyección de SQL en lugar de XSS, o algunas escrituras de archivos arbitrarios reemplazan el código ejecutable en lugar de usar eval )

    
respondido por el Sjoerd Job Postmus 01.03.2016 - 19:16
3

Para que esta función funcione, significa que necesito mantener una capa de reflexión que permita el acceso completo al estado interno de todo el programa.

Para los idiomas interpretados, simplemente puedo usar el estado del intérprete, que es fácil, pero en combinación con los compiladores JIT todavía aumenta significativamente la complejidad.

Sin eval , el compilador JIT a menudo puede probar que no se accede a los datos locales de un subproceso desde cualquier otro código, por lo que es perfectamente aceptable reordenar los accesos, omitir los bloqueos y almacenar en caché los datos que se usan a menudo durante más tiempo. Cuando otro subproceso ejecuta una instrucción eval , puede ser necesario sincronizar el código compilado JIT en ejecución con eso, por lo que de repente el código generado por JIT necesita un mecanismo de retorno que vuelve a la ejecución no optimizada dentro de un período de tiempo razonable.

Este tipo de código tiende a tener muchos errores sutiles y difíciles de reproducir, y al mismo tiempo también pone un límite a la optimización en el compilador JIT.

Para los lenguajes compilados, la compensación es aún peor: la mayoría de las optimizaciones están prohibidas, y necesito mantener una información extensa sobre los símbolos y un intérprete, por lo que la flexibilidad adicional generalmente no vale la pena. A menudo es más fácil definir una interfaz. a algunas estructuras internas, por ejemplo al otorgar acceso simultáneo al modelo del programa view y controller .

    
respondido por el Simon Richter 01.03.2016 - 23:40
1

Rechazo la premisa de que eval se considera más malo que la aritmética de punteros o más peligroso que la memoria directa y el acceso al sistema de archivos. No conozco a ningún desarrollador sensato que pueda creer eso. Además, los idiomas que admiten el acceso directo a la memoria aritmética / puntero no suelen ser compatibles con eval y viceversa, por lo que estoy seguro de la frecuencia con la que tal comparación sería relevante.

Pero eval podría ser una vulnerabilidad más conocida como , por la sencilla razón de que es compatible con JavaScript. JavaScript es un lenguaje de espacio aislado sin acceso directo a la memoria o al sistema de archivos, por lo que simplemente no tiene estas vulnerabilidades, salvo las debilidades en la implementación del lenguaje en sí. Eval es por lo tanto una de las características más peligrosas del lenguaje, ya que abre la posibilidad de ejecución de código arbitrario. Creo que muchos más desarrolladores desarrollan en JavaScript que en C / C ++, por lo que eval es simplemente más importante que los desbordamientos de búfer para la mayoría de los desarrolladores.

    
respondido por el JacquesB 02.03.2016 - 13:31
0

Ningún programador serio consideraría a Eval como "malvado". Es simplemente una herramienta de programación, como cualquier otra. El temor (si hay un temor) a esta función no tiene nada que ver con la cultura popular. Es simplemente un comando peligroso que a menudo se usa incorrectamente y puede introducir graves agujeros de seguridad y degradar el rendimiento. Desde mi propia experiencia, diría que es raro encontrar un problema de programación que no pueda resolverse de forma más segura y eficiente por otros medios. Los programadores que tienen más probabilidades de usar eval son aquellos que probablemente estén menos calificados para hacerlo de manera segura.

Habiendo dicho eso, hay idiomas en los que el uso de eval es apropiado. Perl viene a la mente. Sin embargo, personalmente encuentro que rara vez se necesita en otros lenguajes más modernos, que admiten de forma nativa el manejo estructurado de excepciones.

    
respondido por el user1751825 02.03.2016 - 08:08
0

Creo que lo cubre bastante bien en la siguiente parte de su pregunta (énfasis mío):

  

tienen un buen uso, siempre que se utilicen correctamente

Para el 95% que podría usarlo correctamente, todo está muy bien; pero siempre habrá gente que no lo use adecuadamente. Algunos de ellos serán por falta de experiencia y falta de capacidad, el resto será malicioso.

Siempre habrá gente que quiera forzar los límites y encontrar agujeros de seguridad, algunos para siempre, otros para mal.

En cuanto al aspecto de hecho histórico, las funciones de tipo eval esencialmente permiten Ejecución de código arbitrario que ha sido previamente explotado en la popular web CMS, Joomla! . Con Joomla! Alimentar a más de 2.5 millones de sitios en todo el mundo , eso es un gran daño potencial no solo para los visitantes de esos sitios, sino también para la infraestructura en la que está alojado y también para la reputación de los sitios / compañías que han sido explotados.

La Joomla! el ejemplo puede ser simple, pero se ha grabado.

    
respondido por el gabe3886 02.03.2016 - 11:10
0

Hay algunas buenas razones para desalentar el uso de eval (aunque algunas son específicas de ciertos idiomas).

  • El entorno (léxico y dinámicamente) utilizado por eval es a menudo sorprendente (es decir, piensa que eval(something here) debería hacer una cosa, pero hace otra, posiblemente provocando excepciones)
  • A menudo hay una mejor manera de lograr lo mismo (la concatenación de cierres léxicos construidos es a veces una mejor solución, pero puede que sea específica de Common Lisp)
  • Es demasiado fácil terminar con la evaluación de datos inseguros (aunque esto puede protegerse).

Yo, personalmente, no iría tan lejos como para decir que es malo, pero siempre desafiaré el uso de eval en una revisión de código, con palabras a lo largo de la línea de "has considerado algún código aquí ? " (si tengo tiempo para hacer al menos un reemplazo provisional), o "¿estás seguro de que una evaluación realmente es la mejor solución aquí?" (si no lo hago).

    
respondido por el Vatine 02.03.2016 - 12:55
0

En Society of Mind de Minsky, Capítulo 6.4, dice

  

Hay una una forma en que una mente puede observarse a sí misma y aún así seguir la pista   Qué esta pasando. Divide el cerebro en dos partes, A y B. Conecta   Las entradas y salidas del cerebro A al mundo real, para que pueda sentir   que pasa alli Pero no conectes el cerebro B con el mundo exterior   en absoluto; ¡en lugar de eso, conéctalo para que el cerebro A sea el mundo del cerebro B!

Cuando un programa (B) escribe otro programa (A) está funcionando como un meta-programa. El tema de B es el programa A.

Hay un programa en C. Ese es el que está en su jefe que escribe el programa B.

El peligro es que puede haber alguien (C ') con malas intenciones, que pueda interferir en este proceso, por lo que obtienes cosas como inyección de SQL.

El desafío es hacer que el software sea más inteligente sin que también sea más peligroso.

    
respondido por el Mike Dunlavey 02.03.2016 - 14:26
0

Tiene muchas buenas respuestas aquí y la razón principal es que la ejecución de código arbitrario es mala mmmkay pero agregaré otro factor que otros solo han tocado al borde de:

Es realmente difícil de solucionar el problema del código que se evalúa a partir de una cadena de texto. Sus herramientas de depuración normales son prácticamente directas y quedan reducidos a trazados o ecos de la vieja escuela. Obviamente, eso no importa mucho si tiene un fragmento en una línea que desea eval , pero como un programador experimentado probablemente pueda encontrar una mejor solución para eso, mientras que la generación de código a gran escala puede estar en algún lugar que una función de evaluación se convierte en un aliado potencialmente útil.

    
respondido por el glenatron 03.03.2016 - 13:37

Lea otras preguntas en las etiquetas