Domanda

Sto scrivendo del codice JavaScript per analizzare le funzioni immesse dall'utente (per funzionalità simili a fogli di calcolo). Dopo aver analizzato la formula I potrebbe convertirlo in JavaScript ed eseguire eval () per ottenere il risultato.

Tuttavia, ho sempre evitato di usare eval () se posso evitarlo perché è malvagio (e, giustamente o erroneamente, ho sempre pensato che fosse ancora più malvagio in JavaScript , poiché il codice da valutare potrebbe essere modificato dall'utente).

Quindi, quando è OK usarlo?

È stato utile?

Soluzione

Vorrei prendere un momento per affrontare la premessa della tua domanda - che eval () è " male " ;. La parola " male ", usata dalla gente del linguaggio di programmazione, di solito significa "pericoloso", o più precisamente "capace di causare molti danni con un comando dall'aspetto semplice". Quindi, quando va bene usare qualcosa di pericoloso? Quando sai qual è il pericolo e quando stai prendendo le precauzioni appropriate.

Al punto, diamo un'occhiata ai pericoli nell'uso di eval (). Probabilmente ci sono molti piccoli pericoli nascosti proprio come qualsiasi altra cosa, ma i due grandi rischi - il motivo per cui eval () è considerato malvagio - sono le prestazioni e l'iniezione di codice.

  • Performance - eval () esegue l'interprete / compilatore. Se il tuo codice è compilato, allora questo è un grande successo, perché devi chiamare un compilatore possibilmente pesante nel mezzo del runtime. Tuttavia, JavaScript è ancora principalmente un linguaggio interpretato, il che significa che chiamare eval () non è un grande successo di prestazioni nel caso generale (ma vedi le mie osservazioni specifiche di seguito).
  • Code injection - eval () potenzialmente esegue una stringa di codice con privilegi elevati. Ad esempio, un programma in esecuzione come amministratore / root non vorrebbe mai eval () l'input dell'utente, poiché tale input potrebbe potenzialmente essere " rm -rf / etc / important-file " o peggio. Ancora una volta, JavaScript in un browser non ha questo problema, perché il programma è comunque in esecuzione nell'account dell'utente. JavaScript sul lato server potrebbe avere questo problema.

Passa al tuo caso specifico. Da quello che ho capito, stai generando tu stesso le stringhe, quindi supponendo che stai attento a non consentire una stringa come " rm -rf qualcosa di importante " da generare, non vi è alcun rischio di iniezione di codice (ma ricordate, è molto molto difficile garantire questo nel caso generale). Inoltre, se stai eseguendo il browser, l'iniezione di codice è un rischio piuttosto lieve, credo.

Per quanto riguarda le prestazioni, dovrai ponderarlo contro la facilità di programmazione. È mia opinione che se stai analizzando la formula, potresti anche calcolare il risultato durante l'analisi piuttosto che eseguire un altro parser (quello all'interno di eval ()). Ma potrebbe essere più facile codificare usando eval () e il colpo di performance sarà probabilmente impercettibile. Sembra che eval () in questo caso non sia più malvagio di qualsiasi altra funzione che potrebbe farti risparmiare un po 'di tempo.

Altri suggerimenti

eval () non è male. Oppure, se lo è, è male allo stesso modo in cui riflessione, I / O file / rete, threading e IPC sono "cattivi". in altre lingue.

Se per i tuoi scopi , eval () è più veloce dell'interpretazione manuale, o rende il tuo codice più semplice o più chiaro ... allora dovresti usarlo. Se nessuno dei due, allora non dovresti. Semplice come quello.

Quando ti fidi della fonte.

Nel caso di JSON, è più o meno difficile manomettere l'origine, poiché proviene da un server Web che controlli. Fintanto che lo stesso JSON non contiene dati caricati da un utente, non ci sono grossi svantaggi nell'utilizzo di eval.

In tutti gli altri casi farei di tutto per assicurarmi che i dati forniti dagli utenti siano conformi alle mie regole prima di inviarli a eval ().

Vediamo gente vera:

  1. Ogni browser principale ora ha una console integrata che il tuo potenziale hacker può usare con abbondanza per invocare qualsiasi funzione con qualsiasi valore - perché dovrebbero preoccuparsi di usare un'istruzione eval - anche se potessero?

  2. Se ci vogliono 0,2 secondi per compilare 2000 righe di JavaScript, qual è il mio degrado delle prestazioni se valuto quattro righe di JSON?

Anche la spiegazione di Crockford per "eval is evil" è debole.

  

eval è il male, la funzione eval è la caratteristica più abusata di   JavaScript. Evitalo

Come potrebbe dire lo stesso Crockford "Questo tipo di affermazione tende a generare nevrosi irrazionali. Non comprarlo. & Quot;

Comprendere l'eval e sapere quando potrebbe essere utile è molto più importante. Ad esempio, eval è uno strumento sensato per valutare le risposte del server generate dal software.

A proposito: Prototype.js chiama eval direttamente cinque volte (incluso in evalJSON () e evalResponse ()). jQuery lo usa in parseJSON (tramite il costruttore di funzioni).

Tendo a seguire i consigli di Crockford per eval () , ed evitarlo del tutto. Anche i modi che sembrano richiederlo non lo fanno. Ad esempio, setTimeout () consente di passare una funzione anziché eval.

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

Anche se è una fonte attendibile , non la uso, perché il codice restituito da JSON potrebbe essere confuso, cosa che nella migliore delle ipotesi potrebbe fare qualcosa di traballante, nel peggiore dei casi, esporre qualcosa di brutto.

Eval è complementare alla compilazione che viene utilizzata nel modello del codice. Per modello intendo che scrivi un generatore di template semplificato che genera un utile codice template che aumenta la velocità di sviluppo.

Ho scritto un framework in cui gli sviluppatori non usano EVAL, ma usano il nostro framework e a sua volta quel framework deve usare EVAL per generare modelli.

Le prestazioni di EVAL possono essere aumentate usando il seguente metodo; invece di eseguire lo script, è necessario restituire una funzione.

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

Dovrebbe essere organizzato come

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

var a = f(3,5);

La memorizzazione nella cache f migliorerà sicuramente la velocità.

Anche Chrome consente il debug di tali funzioni molto facilmente.

Per quanto riguarda la sicurezza, l'utilizzo di eval o no non farà praticamente alcuna differenza,

  1. Innanzitutto, il browser richiama l'intero script in una sandbox.
  2. Qualsiasi codice dannoso in EVAL è dannoso nel browser stesso. L'aggressore o chiunque può facilmente iniettare un nodo di script in DOM e fare qualsiasi cosa se può valutare qualsiasi cosa. Non usare EVAL non farà alcuna differenza.
  3. È soprattutto una scarsa sicurezza sul lato server che è dannosa. Una scarsa convalida dei cookie o una scarsa implementazione dell'ACL sul server causano la maggior parte degli attacchi.
  4. Una recente vulnerabilità di Java, ecc. era presente nel codice nativo di Java. JavaScript era ed è progettato per funzionare in un sandbox, mentre le applet sono state progettate per funzionare all'esterno di un sandbox con certificati, ecc. Che portano a vulnerabilità e molte altre cose.
  5. Scrivere codice per imitare un browser non è difficile. Tutto quello che devi fare è inviare una richiesta HTTP al server con la tua stringa agente utente preferita. Tutti gli strumenti di test simulano comunque i browser; se un attaccante vuole farti del male, EVAL è la sua ultima risorsa. Esistono molti altri modi per gestire la sicurezza lato server.
  6. Il DOM del browser non ha accesso ai file e non un nome utente. In realtà nulla sulla macchina a cui Eval può dare accesso.

Se la tua sicurezza lato server è abbastanza solida da consentire a chiunque di attaccare da qualsiasi luogo, non dovresti preoccuparti di EVAL. Come ho già detto, se EVAL non esistesse, gli aggressori hanno molti strumenti per hackerare il tuo server indipendentemente dalla capacità EVAL del tuo browser.

Eval è utile solo per generare alcuni modelli per eseguire elaborazioni complesse di stringhe basate su qualcosa che non viene utilizzato in anticipo. Ad esempio, preferirò

"FirstName + ' ' + LastName"

Al contrario di

"LastName + ' ' + FirstName"

Come il mio nome visualizzato, che può provenire da un database e che non è codificato.

Ho visto le persone incoraggiare a non usare eval, perché è malvagio , ma ho visto le stesse persone usare Funzione e setTimeout in modo dinamico, quindi usano eval sotto i cofani : D

A proposito, se il tuo sandbox non è abbastanza sicuro (ad esempio, se stai lavorando su un sito che consente l'iniezione di codice) eval è l'ultimo dei tuoi problemi. La regola di base della sicurezza è che tutto l'input è male, ma in caso di JavaScript anche JavaScript stesso potrebbe essere male, perché in JavaScript puoi sovrascrivere qualsiasi funzione e puoi semplicemente assicurati di utilizzare quello vero, quindi, se un codice dannoso inizia prima di te, non puoi fidarti di nessuna funzione integrata di JavaScript: D

Ora l'epilogo di questo post è:

Se REALMENTE ne hai bisogno (l'80% delle volte è necessario NON ) e sei sicuro di ciò che stai facendo, usa eval (o migliore funzione;)), chiusure e OOP coprono l'80 / 90% del caso in cui eval può essere sostituito utilizzando un altro tipo di logica, il resto è codice generato dinamicamente (ad esempio, se stai scrivendo un interprete) e mentre già detto valutando JSON (qui puoi usare la valutazione sicura di Crockford;))

Durante il debug in Chrome (v28.0.1500.72), ho scoperto che le variabili non sono legate alle chiusure se non vengono utilizzate in una funzione nidificata che produce la chiusura. Immagino sia un'ottimizzazione del motore JavaScript.

MA : quando eval () viene utilizzato all'interno di una funzione che provoca una chiusura, TUTTO le variabili delle funzioni esterne sono legate al chiusura, anche se non vengono affatto utilizzate. Se qualcuno ha il tempo di testare se possono essere prodotte perdite di memoria, per favore lasciami un commento qui sotto.

Ecco il mio codice di prova:

(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();
})();

Quello che mi piace sottolineare qui è che eval () non deve necessariamente fare riferimento alla funzione nativa eval () . Tutto dipende dal nome della funzione . Quindi quando si chiama il eval () nativo con un nome alias (diciamo var noval = eval; e quindi in una funzione interna noval (espressione); ) quindi la valutazione di espressione potrebbe non riuscire quando si riferisce a variabili che dovrebbero far parte della chiusura, ma in realtà non lo è.

Microsoft spiega perché eval () è lento nel suo browser sul blog di IE, IE + JavaScript Performance Suggerimenti Parte 2: Inefficienze del codice JavaScript .

L'unica istanza in cui dovresti usare eval () è quando devi eseguire JS dinamico al volo. Sto parlando di JS che scarichi in modo asincrono dal server ...

... E 9 volte su 10 potresti facilmente evitare di farlo refactoring.

Va ??bene usarlo se hai il controllo completo sul codice passato alla funzione eval .

eval è raramente la scelta giusta. Mentre ci possono essere numerosi casi in cui puoi realizzare ciò che devi realizzare concatenando uno script insieme ed eseguendolo al volo, in genere hai a tua disposizione tecniche molto più potenti e gestibili: notazione associativa-array ( obj [ " prop "] è lo stesso di obj.prop ), chiusure, tecniche orientate agli oggetti, tecniche funzionali - usali invece.

Per quanto riguarda lo script client, penso che il problema della sicurezza sia un punto controverso. Tutto ciò che viene caricato nel browser è soggetto a manipolazione e deve essere trattato come tale. Non vi è alcun rischio nell'uso di un'istruzione eval () quando esistono modi molto più semplici per eseguire il codice JavaScript e / o manipolare oggetti nel DOM, come la barra degli URL nel browser.

javascript:alert("hello");

Se qualcuno vuole manipolare il proprio DOM, dico swing away. La sicurezza per prevenire qualsiasi tipo di attacco dovrebbe sempre essere la responsabilità dell'applicazione server, punto.

Da un punto di vista pragmatico, non c'è alcun vantaggio nell'usare un eval () in una situazione in cui le cose possono essere fatte diversamente. Tuttavia, ci sono casi specifici in cui DOVREBBE essere usato un eval. In tal caso, può sicuramente essere fatto senza alcun rischio di far saltare in aria la pagina.

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

Quando JavaScript () non è male?

Cerco sempre di scoraggiare dall'uso di eval . Quasi sempre, è disponibile una soluzione più pulita e mantenibile. Eval non è necessario nemmeno per l'analisi JSON . Eval aggiunge all'inferno di manutenzione . Non senza motivo, è malvisto da maestri come Douglas Crockford.

Ma ho trovato un esempio in cui dovrebbe essere usato :

Quando è necessario passare l'espressione.

Ad esempio, ho una funzione che costruisce un google.maps.ImageMapType per me, ma devo dirgli la ricetta, come dovrebbe costruire l'URL del riquadro da zoom e coord parametri:

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);
        },
        ....
    });
}

Il mio esempio di utilizzo di eval : import .

Come si fa di solito.

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

Ma con l'aiuto di eval e una piccola funzione di aiuto si ottiene un aspetto molto migliore:

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

importable potrebbe apparire (questa versione non supporta l'importazione di membri concreti).

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;
}

Sul lato server, eval è utile quando si hanno a che fare con script esterni come sql o influxdb o mongo. Dove è possibile effettuare la convalida personalizzata in fase di runtime senza distribuire nuovamente i servizi.

Ad esempio un servizio di realizzazione con i seguenti metadati

{
  "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\"}`"
  }
}

Che quindi consentono,

  • Iniezione diretta di oggetti / valori attraverso una stringa letterale in un json, utile per creare modelli di testo

  • Può essere usato come comparatore, diciamo che stabiliamo regole su come validare quest o eventi in CMS

Con questo:

  • Possono essere errori nel codice e interrompere elementi nel servizio, se non completamente testati.

  • Se un hacker può scrivere script sul tuo sistema, allora sei praticamente fregato.

  • Un modo per convalidare lo script è mantenere l'hash degli script in un posto sicuro, in modo da poterli controllare prima di eseguirlo.

Penso che tutti i casi in cui giustificare l'eval sarebbero rari. È più probabile che tu lo usi pensando che sia giustificato di quanto tu lo sia quando è effettivamente giustificato.

I problemi di sicurezza sono i più noti. Ma ricorda anche che JavaScript utilizza la compilazione JIT e questo funziona molto male con eval. Eval è in qualche modo simile a una blackbox per il compilatore e JavaScript deve essere in grado di prevedere il codice in anticipo (in una certa misura) al fine di applicare in modo sicuro e corretto le ottimizzazioni delle prestazioni e l'ambito. In alcuni casi, l'impatto sulle prestazioni può persino influenzare altri codici al di fuori di eval.

Se vuoi saperne di più: https: //github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval

Solo durante il test, se possibile. Si noti inoltre che eval () è molto più lento di altri valutatori specializzati JSON ecc.

Generazione di codice. Di recente ho scritto una libreria chiamata Hyperbars che colma il divario tra virtual-dom e manubri . Lo fa analizzando un modello di manubrio e convertendolo in hyperscript . L'iperscript viene generato prima come stringa e prima di restituirlo, eval () per trasformarlo in codice eseguibile. Ho trovato eval () in questa situazione particolare l'esatto contrario del male.

Fondamentalmente da

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

A questo

(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({}))

Le prestazioni di eval () non sono un problema anche in una situazione come questa perché è necessario interpretare la stringa generata una sola volta e riutilizzare l'output eseguibile più volte.

Puoi vedere come è stata raggiunta la generazione del codice se sei curioso qui .

Non c'è motivo di non usare eval () purché si possa essere sicuri che l'origine del codice provenga da te o dall'utente reale. Anche se è in grado di manipolare ciò che viene inviato nella funzione eval (), questo non è un problema di sicurezza, perché è in grado di manipolare il codice sorgente del sito Web e potrebbe quindi modificare il codice JavaScript stesso.

Quindi ... quando non usare eval ()? Eval () non dovrebbe essere usato solo quando esiste la possibilità che una terza parte possa cambiarlo. Come intercettare la connessione tra il client e il tuo server (ma se questo è un problema usa HTTPS). Non dovresti eval () per analizzare il codice scritto da altri come in un forum.

Se è veramente necessario, eval non è male. Ma il 99,9% degli usi di eval in cui inciampo sono non necessari (ad esclusione di roba setTimeout).

Per me il male non è una prestazione o addirittura un problema di sicurezza (beh, indirettamente è entrambi). Tutti questi usi non necessari di eval si aggiungono a un inferno di manutenzione. Gli strumenti di refactoring vengono eliminati. La ricerca del codice è difficile. Gli effetti imprevisti di questi eval sono la legione.

La mia convinzione è che eval sia una funzione molto potente per applicazioni web lato client e sicura ... Sicuro come JavaScript, che non lo sono. :-) I problemi di sicurezza sono essenzialmente un problema lato server perché, ora, con uno strumento come Firebug, puoi attaccare qualsiasi applicazione JavaScript.

Eval è utile per la generazione di codice quando non si hanno macro.

Per un (stupido) esempio, se stai scrivendo un compilatore Brainfuck , tu probabilmente vorrai costruire una funzione che esegua la sequenza di istruzioni come una stringa e valutarla per restituire una funzione.

Quando si analizza una struttura JSON con una funzione di analisi (ad esempio, jQuery.parseJSON), si aspetta una struttura perfetta del file JSON (ogni nome di proprietà è tra virgolette). Tuttavia, JavaScript è più flessibile. Pertanto, è possibile utilizzare eval () per evitarlo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top