Pregunta

Estoy escribiendo un código JavaScript para analizar las funciones ingresadas por el usuario (para una funcionalidad similar a una hoja de cálculo). Después de analizar la fórmula, podría convertirla en JavaScript y ejecutar eval () en ella para obtener el resultado.

Sin embargo, siempre he evitado usar eval () si puedo evitarlo porque es malo (y, correcta o incorrectamente, siempre he pensado que es aún más malo en JavaScript , porque el usuario puede cambiar el código a evaluar).

Entonces, ¿cuándo está bien usarlo?

¿Fue útil?

Solución

Me gustaría tomarme un momento para abordar la premisa de su pregunta: que eval () es " evil " ;. La palabra `` maldad '', como la usan las personas del lenguaje de programación, generalmente significa `` peligroso '', o más precisamente `` capaz de causar mucho daño con un comando simple ''. Entonces, ¿cuándo está bien usar algo peligroso? Cuando sepa cuál es el peligro y cuando esté tomando las precauciones adecuadas.

Hasta el punto, veamos los peligros en el uso de eval (). Probablemente hay muchos pequeños peligros ocultos como todo lo demás, pero los dos grandes riesgos, la razón por la cual eval () se considera malvado, son el rendimiento y la inyección de código.

  • Rendimiento: eval () ejecuta el intérprete / compilador. Si su código está compilado, entonces este es un gran éxito, porque necesita llamar a un compilador posiblemente pesado en medio del tiempo de ejecución. Sin embargo, JavaScript sigue siendo principalmente un lenguaje interpretado, lo que significa que llamar a eval () no es un gran éxito de rendimiento en el caso general (pero vea mis comentarios específicos a continuación).
  • Inyección de código: eval () potencialmente ejecuta una cadena de código con privilegios elevados. Por ejemplo, un programa que se ejecuta como administrador / root nunca querría evaluar la entrada del usuario (), porque esa entrada podría ser "rm -rf / etc / important-file". o peor. Nuevamente, JavaScript en un navegador no tiene ese problema, porque el programa se ejecuta en la cuenta del usuario de todos modos. JavaScript del lado del servidor podría tener ese problema.

A su caso específico. Por lo que entiendo, estás generando las cadenas tú mismo, así que suponiendo que tengas cuidado de no permitir una cadena como "rm -rf something-important". para generar, no hay riesgo de inyección de código (pero recuerde, es muy muy difícil asegurar esto en el caso general). Además, si está ejecutando en el navegador, la inyección de código es un riesgo bastante menor, creo.

En cuanto al rendimiento, tendrá que compararlo con la facilidad de codificación. Es mi opinión que si está analizando la fórmula, también podría calcular el resultado durante el análisis en lugar de ejecutar otro analizador (el que está dentro de eval ()). Pero puede ser más fácil codificar usando eval (), y el impacto en el rendimiento probablemente pasará desapercibido. Parece que eval () en este caso no es más malvado que cualquier otra función que pueda ahorrarle algo de tiempo.

Otros consejos

eval () no es malo. O, si es así, es malo de la misma manera que la reflexión, la E / S de archivo / red, el enhebrado y el IPC son "malos". en otros idiomas.

Si, para su propósito , eval () es más rápido que la interpretación manual, o hace que su código sea más simple o más claro ... entonces debe usarlo. Si no es así, entonces no deberías. Tan simple como eso.

Cuando confías en la fuente.

En el caso de JSON, es más o menos difícil alterar la fuente, ya que proviene de un servidor web que usted controla. Mientras el JSON en sí no contenga datos que haya cargado un usuario, no hay inconvenientes importantes para usar eval.

En todos los demás casos, haría todo lo posible para garantizar que los datos proporcionados por el usuario se ajusten a mis reglas antes de enviarlos a eval ().

Consigamos gente real:

  1. Todos los principales navegadores ahora tienen una consola incorporada que su posible pirata informático puede usar con abundancia para invocar cualquier función con algún valor, ¿por qué se molestarían en usar una declaración de evaluación, incluso si pudieran? / p>

  2. Si toma 0.2 segundos compilar 2000 líneas de JavaScript, ¿cuál es mi degradación del rendimiento si evalúo cuatro líneas de JSON?

Incluso la explicación de Crockford para 'eval is evil' es débil.

  

eval es malvado, la función eval es la característica más mal utilizada de   JavaScript Evítalo

Como el mismo Crockford podría decir "Este tipo de afirmación tiende a generar neurosis irracional". No lo compres.

Comprender eval y saber cuándo podría ser útil es mucho más importante. Por ejemplo, eval es una herramienta sensata para evaluar las respuestas del servidor que fueron generadas por su software.

Por cierto: Prototype.js llama a eval directamente cinco veces (incluso en evalJSON () y evalResponse ()). jQuery lo usa en parseJSON (a través del constructor de funciones).

Tiendo a seguir Consejos de Crockford para eval () , y evitarlo por completo. Incluso las formas que parecen requerirlo no lo hacen. Por ejemplo, el setTimeout () le permite pasar una función en lugar de evaluar.

setTimeout(function() {
  alert('hi');
}, 1000);

Incluso si se trata de una fuente confiable , no lo uso, porque el código devuelto por JSON podría estar confuso, lo que en el mejor de los casos podría hacer algo inestable, en el peor de los casos, exponer algo malo.

Eval es complementario a la compilación que se usa para crear plantillas del código. Por plantilla quiero decir que escribes un generador de plantillas simplificado que genera un código de plantilla útil que aumenta la velocidad de desarrollo.

He escrito un marco, donde los desarrolladores no usan EVAL, pero usan nuestro marco y, a su vez, ese marco tiene que usar EVAL para generar plantillas.

El rendimiento de EVAL se puede aumentar utilizando el siguiente método; en lugar de ejecutar el script, debe devolver una función.

var a = eval("3 + 5");

Debería estar organizado como

var f = eval("(function(a,b) { return a + b; })");

var a = f(3,5);

El almacenamiento en caché f ciertamente mejorará la velocidad.

También Chrome permite la depuración de tales funciones muy fácilmente.

Con respecto a la seguridad, el uso de eval o not casi no hará ninguna diferencia,

  1. En primer lugar, el navegador invoca todo el script en una caja de arena.
  2. Cualquier código que sea malo en EVAL, es malo en el navegador mismo. El atacante o cualquier persona puede inyectar fácilmente un nodo de script en DOM y hacer cualquier cosa si puede evaluar cualquier cosa. No usar EVAL no hará ninguna diferencia.
  3. La mayor parte de la seguridad del lado del servidor es dañina. La mala validación de las cookies o la implementación deficiente de ACL en el servidor causa la mayoría de los ataques.
  4. Una vulnerabilidad reciente de Java, etc. estaba presente en el código nativo de Java. JavaScript fue y está diseñado para ejecutarse en un sandbox, mientras que los applets fueron diseñados para ejecutarse fuera de un sandbox con certificados, etc. que generan vulnerabilidades y muchas otras cosas.
  5. Escribir código para imitar un navegador no es difícil. Todo lo que tiene que hacer es hacer una solicitud HTTP al servidor con su cadena de agente de usuario favorita. Todas las herramientas de prueba se burlan de los navegadores de todos modos; Si un atacante quiere hacerte daño, EVAL es su último recurso. Tienen muchas otras formas de lidiar con la seguridad del lado del servidor.
  6. El navegador DOM no tiene acceso a los archivos y no tiene un nombre de usuario. De hecho, nada en la máquina a la que eval pueda dar acceso.

Si la seguridad del lado del servidor es lo suficientemente sólida como para que cualquiera pueda atacar desde cualquier lugar, no debe preocuparse por EVAL. Como mencioné, si EVAL no existiera, los atacantes tienen muchas herramientas para hackear su servidor independientemente de la capacidad EVAL de su navegador.

Eval solo es bueno para generar algunas plantillas para hacer un procesamiento de cadenas complejo basado en algo que no se usa de antemano. Por ejemplo, preferiré

"FirstName + ' ' + LastName"

A diferencia de

"LastName + ' ' + FirstName"

Como mi nombre para mostrar, que puede provenir de una base de datos y que no está codificado.

Vi que la gente aboga por no usar eval, porque es malvado , pero vi que las mismas personas usan Function y setTimeout dinámicamente, por lo que usan eval debajo de las campanas : D

Por cierto, si su sandbox no está lo suficientemente seguro (por ejemplo, si está trabajando en un sitio que permite la inyección de código) eval es el último de sus problemas. La regla básica de seguridad es que todas las entradas son malas, pero en el caso de JavaScript incluso JavaScript en sí mismo podría ser malo, porque en JavaScript puede sobrescribir cualquier función y simplemente puede no esté seguro de que está usando el verdadero, por lo tanto, si un código malicioso comienza antes que usted, no puede confiar en ninguna función incorporada de JavaScript: D

Ahora el epílogo de esta publicación es:

Si REALMENTE lo necesita (el 80% del tiempo eval no es NO ) y está seguro de lo que está haciendo, simplemente use eval (o Mejor función;)), los cierres y la OOP cubren el 80/90% del caso donde eval puede ser reemplazado usando otro tipo de lógica, el resto es código generado dinámicamente (por ejemplo, si está escribiendo un intérprete) y como usted ya dijo evaluar JSON (aquí puede usar la evaluación segura de Crockford;))

Al depurar en Chrome (v28.0.1500.72), descubrí que las variables no están vinculadas a cierres si no se usan en una función anidada que produce el cierre. Supongo que es una optimización del motor de JavaScript.

PERO : cuando se usa eval () dentro de una función que causa un cierre, ALL las variables de funciones externas están vinculadas a cierre, incluso si no se utilizan en absoluto. Si alguien tiene tiempo para probar si eso puede producir pérdidas de memoria, déjeme un comentario a continuación.

Aquí está mi código de prueba:

(function () {
    var eval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();   // Variable "unused" is visible in debugger
            eval("1");
        })();
    }

    evalTest();
})();

(function () {
    var eval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();   // Variable "unused" is NOT visible in debugger
            var noval = eval;
            noval("1");
        })();
    }

    evalTest();
})();

(function () {
    var noval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();    // Variable "unused" is NOT visible in debugger
            noval("1");
        })();
    }

    evalTest();
})();

Lo que me gustaría señalar aquí es que eval () no necesariamente debe referirse a la función nativa eval () . Todo depende del nombre de la función . Entonces, al llamar al eval () nativo con un nombre de alias (digamos var noval = eval; y luego en una función interna noval (expresión); ) entonces la evaluación de expresión puede fallar cuando se refiere a variables que deberían ser parte del cierre, pero en realidad no lo es.

Microsoft explica por qué eval () es lento en su navegador en el Blog de IE, Recomendaciones de rendimiento de IE + JavaScript Parte 2: Ineficiencias de código JavaScript .

La única instancia en la que debería usar eval () es cuando necesita ejecutar JS dinámico sobre la marcha. Estoy hablando de JS que descargas de forma asíncrona desde el servidor ...

... Y 9 de cada 10 veces podría evitarlo fácilmente refactorizando.

Está bien usarlo si tiene control completo sobre el código que se pasa a la función eval .

eval rara vez es la elección correcta. Si bien puede haber numerosos casos en los que puede lograr lo que necesita lograr concatenando un script y ejecutándolo sobre la marcha, por lo general tiene técnicas mucho más potentes y fáciles de mantener a su disposición: notación de matriz asociativa ( obj [ " prop "] es lo mismo que obj.prop ), cierres, técnicas orientadas a objetos, técnicas funcionales: utilícelas en su lugar.

En lo que respecta al script del cliente, creo que el tema de la seguridad es un punto discutible. Todo lo cargado en el navegador está sujeto a manipulación y debe tratarse como tal. Existe un riesgo cero al usar una declaración eval () cuando hay formas mucho más fáciles de ejecutar código JavaScript y / o manipular objetos en el DOM, como la barra de URL en su navegador.

javascript:alert("hello");

Si alguien quiere manipular su DOM, le digo que se aleje. La seguridad para evitar cualquier tipo de ataque siempre debe ser responsabilidad de la aplicación del servidor, punto.

Desde un punto de vista pragmático, no es beneficioso usar un eval () en una situación en la que las cosas se pueden hacer de otra manera. Sin embargo, hay casos específicos en los que DEBE usarse una evaluación. Cuando sea así, definitivamente se puede hacer sin ningún riesgo de volar la página.

<html>
    <body>
        <textarea id="output"></textarea><br/>
        <input type="text" id="input" />
        <button id="button" onclick="execute()">eval</button>

        <script type="text/javascript">
            var execute = function(){
                var inputEl = document.getElementById('input');
                var toEval = inputEl.value;
                var outputEl = document.getElementById('output');
                var output = "";

                try {
                    output = eval(toEval);
                }
                catch(err){
                    for(var key in err){
                        output += key + ": " + err[key] + "\r\n";
                    }
                }
                outputEl.value = output;
            }
        </script>
    <body>
</html>

¿Cuándo no es malo el eval () de JavaScript?

Siempre trato de desalentar el uso de eval . Casi siempre, hay disponible una solución más limpia y fácil de mantener. Eval no es necesario incluso para el análisis JSON . Eval se suma al infierno de mantenimiento . No sin razón, está mal visto por maestros como Douglas Crockford.

Pero encontré un ejemplo en el que debería usarse :

Cuando necesita pasar la expresión.

Por ejemplo, tengo una función que construye un general google.maps.ImageMapType objeto para mí, pero necesito decirle la receta, ¿cómo debería construir la URL del mosaico a partir del zoom y parámetros de coord :

my_func({
    name: "OSM",
    tileURLexpr: '"http://tile.openstreetmap.org/"+b+"/"+a.x+"/"+a.y+".png"',
    ...
});

function my_func(opts)
{
    return new google.maps.ImageMapType({
        getTileUrl: function (coord, zoom) {
            var b = zoom;
            var a = coord;
            return eval(opts.tileURLexpr);
        },
        ....
    });
}

Mi ejemplo de uso de eval : import .

Cómo se hace generalmente.

var components = require('components');
var Button = components.Button;
var ComboBox = components.ComboBox;
var CheckBox = components.CheckBox;
...
// That quickly gets very boring

Pero con la ayuda de eval y una pequeña función auxiliar, se ve mucho mejor:

var components = require('components');
eval(importable('components', 'Button', 'ComboBox', 'CheckBox', ...));

importable podría verse así (esta versión no admite la importación de miembros concretos).

function importable(path) {
    var name;
    var pkg = eval(path);
    var result = '\n';

    for (name in pkg) {
        result += 'if (name !== undefined) throw "import error: name already exists";\n'.replace(/name/g, name);
    }

    for (name in pkg) {
        result += 'var name = path.name;\n'.replace(/name/g, name).replace('path', path);
    }
    return result;
}

En el lado del servidor eval es útil cuando se trata de scripts externos como sql o influxdb o mongo. Donde se puede realizar una validación personalizada en tiempo de ejecución sin volver a implementar sus servicios.

Por ejemplo, un servicio de logros con los siguientes metadatos

{
  "568ff113-abcd-f123-84c5-871fe2007cf0": {
    "msg_enum": "quest/registration",
    "timely": "all_times",
    "scope": [
      "quest/daily-active"
    ],
    "query": "`SELECT COUNT(point) AS valid from \"${userId}/dump/quest/daily-active\" LIMIT 1`",
    "validator": "valid > 0",
    "reward_external": "ewallet",
    "reward_external_payload": "`{\"token\": \"${token}\", \"userId\": \"${userId}\", \"amountIn\": 1, \"conversionType\": \"quest/registration:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/registration\"}`"
  },
  "efdfb506-1234-abcd-9d4a-7d624c564332": {
    "msg_enum": "quest/daily-active",
    "timely": "daily",
    "scope": [
      "quest/daily-active"
    ],
    "query": "`SELECT COUNT(point) AS valid from \"${userId}/dump/quest/daily-active\" WHERE time >= '${today}' ${ENV.DAILY_OFFSET} LIMIT 1`",
    "validator": "valid > 0",
    "reward_external": "ewallet",
    "reward_external_payload": "`{\"token\": \"${token}\", \"userId\": \"${userId}\", \"amountIn\": 1, \"conversionType\": \"quest/daily-active:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/daily-active\"}`"
  }
}

Que luego permiten,

  • Inyección directa de objetos / valores a través de una cadena literal en un json, útil para crear plantillas

  • Se puede usar como un comparador, digamos que hacemos reglas sobre cómo validar misiones o eventos en CMS

Con de esto:

  • Puede haber errores en el código y romper cosas en el servicio, si no se prueba completamente.

  • Si un hacker puede escribir un script en su sistema, entonces está bastante jodido.

  • Una forma de validar su script es mantener el hash de sus scripts en un lugar seguro, para que pueda verificarlos antes de ejecutarlos.

Creo que cualquier caso de evaluación justificada sería raro. Es más probable que lo use pensando que está justificado que cuando está realmente justificado.

Los problemas de seguridad son los más conocidos. Pero también tenga en cuenta que JavaScript usa la compilación JIT y esto funciona muy mal con eval. Eval es algo así como una caja negra para el compilador, y JavaScript necesita poder predecir el código con anticipación (hasta cierto punto) para aplicar de manera segura y correcta las optimizaciones de rendimiento y el alcance. En algunos casos, el impacto en el rendimiento puede incluso afectar a otro código fuera de eval.

Si quieres saber más: https: //github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval

Solo durante las pruebas, si es posible. También tenga en cuenta que eval () es mucho más lento que otros evaluadores JSON especializados, etc.

Generación de código. Recientemente escribí una biblioteca llamada Hyperbars que cierra la brecha entre virtual-dom y manillar . Para ello, analiza una plantilla de manillar y la convierte en hyperscript . El hiperescrito se genera primero como una cadena y antes de devolverlo, eval () para convertirlo en código ejecutable. He encontrado eval () en esta situación particular, exactamente lo contrario del mal.

Básicamente de

<div>
    {{#each names}}
        <span>{{this}}</span>
    {{/each}}
</div>

A esto

(function (state) {
    var Runtime = Hyperbars.Runtime;
    var context = state;
    return h('div', {}, [Runtime.each(context['names'], context, function (context, parent, options) {
        return [h('span', {}, [options['@index'], context])]
    })])
}.bind({}))

El rendimiento de eval () tampoco es un problema en una situación como esta porque solo necesita interpretar la cadena generada una vez y luego reutilizar la salida ejecutable muchas veces.

Puede ver cómo se logró la generación del código si tiene curiosidad aquí .

No hay razón para no usar eval () siempre y cuando pueda estar seguro de que la fuente del código proviene de usted o del usuario real. Aunque puede manipular lo que se envía a la función eval (), eso no es un problema de seguridad, porque puede manipular el código fuente del sitio web y, por lo tanto, podría cambiar el código JavaScript en sí.

Entonces ... ¿cuándo no usar eval ()? Eval () solo no debe usarse cuando existe la posibilidad de que un tercero pueda cambiarlo. Como interceptar la conexión entre el cliente y su servidor (pero si eso es un problema, use HTTPS). No debe evaluar () para analizar el código escrito por otros como en un foro.

Si realmente se necesita eval no es malo. Pero el 99.9% de los usos de eval con los que me tropiezo son no necesarios (sin incluir cosas de setTimeout).

Para mí, el mal no es un problema de rendimiento o incluso de seguridad (bueno, indirectamente son ambos). Todos esos usos innecesarios de eval se suman a un infierno de mantenimiento. Las herramientas de refactorización se desechan. Buscar código es difícil. Los efectos no anticipados de esas evaluaciones son legión.

Creo que eval es una función muy poderosa para aplicaciones web del lado del cliente y segura ... Tan segura como JavaScript, que no lo son. :-) Los problemas de seguridad son esencialmente un problema del lado del servidor porque, ahora, con herramientas como Firebug, puedes atacar cualquier aplicación de JavaScript.

Eval es útil para la generación de código cuando no tienes macros.

Por ejemplo (estúpido), si está escribiendo un Brainfuck , usted Probablemente querrá construir una función que realice la secuencia de instrucciones como una cadena y evaluarla para devolver una función.

Cuando analiza una estructura JSON con una función de análisis (por ejemplo, jQuery.parseJSON), espera una estructura perfecta del archivo JSON (cada nombre de propiedad está entre comillas dobles). Sin embargo, JavaScript es más flexible. Por lo tanto, puede usar eval () para evitarlo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top