La funzione eval è un modo semplice e potente per generare dinamicamente codice, quindi quali sono le avvertenze?
La funzione eval è un modo semplice e potente per generare dinamicamente codice, quindi quali sono le avvertenze?
Risposte:
L'uso improprio di eval apre il codice per gli attacchi di iniezione
Il debug può essere più impegnativo (nessun numero di riga, ecc.)
il codice eval'd viene eseguito più lentamente (nessuna possibilità di compilare / memorizzare nella cache il codice eval'd)
Modifica: Come sottolinea @Jeff Walden nei commenti, oggi la # 3 è meno vera di quanto non fosse nel 2008. Tuttavia, mentre alcune operazioni di cache degli script compilati possono accadere, ciò sarà limitato solo agli script che verranno valutati ripetuti senza alcuna modifica. Uno scenario più probabile è che stai valutando script che hanno subito lievi modifiche ogni volta e che pertanto non possono essere memorizzati nella cache. Diciamo solo che ALCUNI codici valutati vengono eseguiti più lentamente.
badHackerGuy'); doMaliciousThings();
e se prendi il mio nome utente, lo concatichi in alcuni script e lo valuti nei browser di altre persone, allora posso eseguire qualsiasi javascript che desidero sui loro computer (ad esempio forzarli a fare +1 sui miei post, pubblica i loro dati sul mio server, ecc.)
debugger;
istruzione al codice sorgente. Ciò interromperà l'esecuzione del programma su quella linea. Quindi, è possibile aggiungere punti di interruzione di debug come se fosse solo un altro file JS.
eval non è sempre malvagio. Ci sono momenti in cui è perfettamente appropriato.
Tuttavia, eval è attualmente e storicamente massicciamente utilizzato da persone che non sanno cosa stanno facendo. Ciò include le persone che scrivono tutorial JavaScript, sfortunatamente, e in alcuni casi ciò può effettivamente avere conseguenze sulla sicurezza o, più spesso, semplici bug. Quindi più possiamo fare per gettare un punto interrogativo su eval, meglio è. Ogni volta che usi eval devi controllare la sanità mentale di ciò che stai facendo, perché è probabile che tu possa farlo in un modo migliore, più sicuro e più pulito.
Per fare un esempio fin troppo tipico, per impostare il colore di un elemento con un id memorizzato nella variabile 'potato':
eval('document.' + potato + '.style.color = "red"');
Se gli autori del tipo di codice sopra riportato avessero avuto un'idea delle basi di come funzionano gli oggetti JavaScript, avrebbero capito che è possibile usare parentesi quadre anziché letterali dot-points, ovviando alla necessità di eval:
document[potato].style.color = 'red';
... che è molto più facile da leggere e meno potenzialmente buggy.
(Ma poi, qualcuno che / realmente / sapeva cosa stavano facendo avrebbe detto:
document.getElementById(potato).style.color = 'red';
che è più affidabile del vecchio trucco ingannevole di accedere agli elementi DOM direttamente dall'oggetto documento.)
JSON.parse()
invece che eval()
per JSON?
Credo che sia perché può eseguire qualsiasi funzione JavaScript da una stringa. Usarlo rende più facile per le persone iniettare codice canaglia nell'applicazione.
Mi vengono in mente due punti:
Sicurezza (ma fino a quando si genera la stringa da valutare da soli, questo potrebbe non essere un problema)
Prestazioni: fino a quando il codice da eseguire non è sconosciuto, non può essere ottimizzato. (su javascript e performance, sicuramente la presentazione di Steve Yegge )
eval
e poi quel frammento di codice viene eseguito sul browser B dell'utente
Passare l'input dell'utente a eval () è un rischio per la sicurezza, ma anche ogni invocazione di eval () crea una nuova istanza dell'interprete JavaScript. Questo può essere un porco di risorse.
Principalmente, è molto più difficile da mantenere e eseguire il debug. È come a goto
. Puoi usarlo, ma rende più difficile trovare problemi e più difficile per le persone che potrebbero aver bisogno di apportare modifiche in seguito.
Una cosa da tenere a mente è che spesso puoi usare eval () per eseguire il codice in un ambiente altrimenti limitato - i siti di social network che bloccano specifiche funzioni JavaScript possono a volte essere imbrogliati rompendoli in un blocco eval -
eval('al' + 'er' + 't(\'' + 'hi there!' + '\')');
Quindi, se stai cercando di eseguire un codice JavaScript in cui altrimenti non potrebbe essere consentito ( Myspace , ti sto guardando ...) allora eval () può essere un trucco utile.
Tuttavia, per tutte le ragioni sopra menzionate, non dovresti usarlo per il tuo codice, dove hai il controllo completo - non è solo necessario, e meglio relegato nello scaffale degli "hacker JavaScript complicati".
[]["con"+"struc"+"tor"]["con"+"struc"+"tor"]('al' + 'er' + 't(\'' + 'hi there!' + '\')')()
A meno che non lasci a eval () un contenuto dinamico (tramite cgi o input), è sicuro e solido come tutti gli altri JavaScript nella tua pagina.
Insieme al resto delle risposte, non credo che le dichiarazioni di valutazione possano avere una minimizzazione avanzata.
È un possibile rischio per la sicurezza, ha un diverso ambito di esecuzione ed è abbastanza inefficiente, in quanto crea un ambiente di scripting completamente nuovo per l'esecuzione del codice. Vedi qui per qualche informazione in più: eval .
È abbastanza utile, tuttavia, e usato con moderazione può aggiungere molte buone funzionalità.
A meno che non si sia sicuri al 100% che il codice in fase di valutazione provenga da una fonte attendibile (di solito la propria applicazione), si tratta di un modo infallibile di esporre il sistema a un attacco di scripting tra siti.
So che questa discussione è vecchia, ma mi piace molto questo approccio di Google e volevo condividere questo sentimento con gli altri;)
L'altra cosa è che meglio ottieni e più cerchi di capire e infine non credi che qualcosa sia buono o cattivo solo perché qualcuno l'ha detto :) Questo è un video molto stimolante che mi ha aiutato a pensare di più da solo :) LE BUONE PRATICHE sono buone, ma non usarle senza pensarci :)
Non è necessariamente così male purché tu sappia in quale contesto lo stai usando.
Se l'applicazione sta utilizzando eval()
per creare un oggetto da alcuni JSON che è tornato da un XMLHttpRequest al tuo sito, creato dal tuo codice lato server attendibile, probabilmente non è un problema.
Il codice JavaScript sul lato client non attendibile non può fare altrettanto. A condizione che la cosa su cui stai eseguendo eval()
provenga da una fonte ragionevole, stai bene.
Riduce notevolmente il livello di sicurezza in merito alla sicurezza.
Se si desidera che l'utente inserisca alcune funzioni logiche e valuti AND e OR, la funzione di valutazione JavaScript è perfetta. Posso accettare due stringhe e eval(uate) string1 === string2
, ecc.
Se noti l'uso di eval () nel tuo codice, ricorda che il mantra "eval () è malvagio".
Questa funzione accetta una stringa arbitraria e la esegue come codice JavaScript. Quando il codice in questione è noto in anticipo (non determinato in fase di esecuzione), non c'è motivo di usare eval (). Se il codice viene generato dinamicamente in fase di esecuzione, spesso c'è un modo migliore per raggiungere l'obiettivo senza eval (). Ad esempio, usare la notazione tra parentesi quadre per accedere alle proprietà dinamiche è migliore e più semplice:
// antipattern
var property = "name";
alert(eval("obj." + property));
// preferred
var property = "name";
alert(obj[property]);
L'uso eval()
ha anche implicazioni per la sicurezza, perché potresti eseguire codice (ad esempio proveniente dalla rete) che è stato manomesso. Questo è un antipattern comune quando si ha a che fare con una risposta JSON da una richiesta Ajax. In questi casi è meglio usare i metodi integrati dei browser per analizzare la risposta JSON per assicurarsi che sia sicura e valida. Per i browser che non supportanoJSON.parse()
nativamente, è possibile utilizzare una libreria di JSON.org.
È anche importante ricordare che passare stringhe a setInterval()
, setTimeout()
e il Function()
costruttore è, per la maggior parte, simile all'uso eval()
e quindi dovrebbe essere evitato.
Dietro le quinte, JavaScript deve ancora valutare ed eseguire la stringa che passi come codice di programmazione:
// antipatterns
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);
// preferred
setTimeout(myFunc, 1000);
setTimeout(function () {
myFunc(1, 2, 3);
}, 1000);
L'uso del nuovo costruttore Function () è simile a eval () e dovrebbe essere affrontato con cura. Potrebbe essere un costrutto potente ma è spesso usato in modo improprio. Se devi assolutamente usareeval()
, puoi considerare di usare invece la nuova funzione ().
Vi è un piccolo vantaggio potenziale perché il codice valutato nella nuova funzione () verrà eseguito nell'ambito di una funzione locale, quindi qualsiasi variabile definita con var nel codice che viene valutato non diventerà automaticamente globale.
Un altro modo per prevenire i globi automatici è avvolgere la
eval()
chiamata in una funzione immediata.
Oltre ai possibili problemi di sicurezza se si esegue il codice inviato dall'utente, la maggior parte delle volte esiste un modo migliore che non comporta il riesame del codice ogni volta che viene eseguito. Le funzioni anonime o le proprietà degli oggetti possono sostituire la maggior parte degli usi di eval e sono molto più sicure e veloci.
Questo potrebbe diventare più un problema poiché la prossima generazione di browser esce con un po 'di sapore di un compilatore JavaScript. Il codice eseguito tramite Eval potrebbe non funzionare così come il resto del tuo JavaScript rispetto a questi browser più recenti. Qualcuno dovrebbe fare un po 'di profilazione.
Questo è uno dei buoni articoli che parlano di eval e di come non sia un male: http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/
Non sto dicendo che dovresti esaurirti e iniziare a usare eval () ovunque. In effetti, ci sono pochissimi casi d'uso per eseguire eval (). Ci sono sicuramente dubbi sulla chiarezza del codice, sulla debugabilità e certamente sulle prestazioni che non devono essere trascurate. Ma non dovresti aver paura di usarlo quando hai un caso in cui eval () ha un senso. Prova a non usarlo prima, ma non lasciare che nessuno ti spaventi a pensare che il tuo codice sia più fragile o meno sicuro quando eval () viene usato in modo appropriato.
eval () è molto potente e può essere utilizzato per eseguire un'istruzione JS o valutare un'espressione. Ma la domanda non riguarda gli usi di eval () ma diciamo solo in che modo la stringa che si esegue con eval () è influenzata da una parte malvagia. Alla fine eseguirai codice dannoso. Con il potere derivano grandi responsabilità. Quindi usalo saggiamente se lo stai usando. Questo non è molto legato alla funzione eval () ma questo articolo contiene informazioni piuttosto buone: http://blogs.popart.com/2009/07/javascript-injection-attacks/ Se stai cercando le basi di eval () guarda qui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
Il motore JavaScript ha una serie di ottimizzazioni delle prestazioni che esegue durante la fase di compilazione. Alcuni di questi si riducono alla capacità di analizzare staticamente essenzialmente il codice man mano che si allenta e predeterminare dove si trovano tutte le dichiarazioni di variabili e funzioni, in modo che ci voglia meno sforzo per risolvere gli identificatori durante l'esecuzione.
Ma se il motore trova una valutazione (..) nel codice, essenzialmente deve presumere che tutta la sua consapevolezza della posizione dell'identificatore potrebbe essere non valida, perché non può sapere al momento giusto quale codice si può passare a valutazione (..) per modificare l'ambito lessicale o il contenuto dell'oggetto a cui è possibile passare per creare un nuovo ambito lessicale da consultare.
In altre parole, in senso pessimistico, la maggior parte di quelle ottimizzazioni che avrebbe fatto sono inutili se è presente eval (..), quindi semplicemente non esegue affatto le ottimizzazioni.
Questo spiega tutto.
Riferimento:
https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch2.md#eval
https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch2.md#performance
Non è sempre una cattiva idea. Prendiamo ad esempio la generazione del codice. Di recente ho scritto una libreria chiamata Hyperbars che colma il divario tra virtual-dom e manubrio . Lo fa analizzando un modello di manubrio e convertendolo in iperscript che viene successivamente utilizzato da virtual-dom. L'iperscript viene generato prima come stringa e prima di restituirlo, eval()
per trasformarlo in codice eseguibile. Ho trovato eval()
in questa particolare situazione 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 in una situazione come questa perché è necessario interpretare la stringa generata una sola volta e riutilizzare più volte l'output eseguibile.
Puoi vedere come è stata raggiunta la generazione del codice se sei curioso qui .
Andrei fino al punto di dire che non importa davvero se lo usi eval()
javascript che viene eseguito nei browser. * (Avvertenza)
Tutti i browser moderni hanno una console per sviluppatori in cui è possibile eseguire javascript arbitrari in ogni caso e qualsiasi sviluppatore semi-intelligente può guardare la tua fonte JS e mettere tutti i bit di cui ha bisogno nella console di sviluppo per fare ciò che desidera.
* Fintanto che gli endpoint del server hanno la corretta convalida e sanificazione dei valori forniti dall'utente, non dovrebbe importare ciò che viene analizzato e valutato nel javascript sul lato client.
Se dovessi chiederti se è adatto da usare eval()
in PHP, la risposta è NO , a meno che tu non autorizzi tutti i valori che possono essere passati alla tua dichiarazione di valutazione.
Non tenterò di confutare nulla di ciò che è stato detto finora, ma offrirò questo uso di eval () che (per quanto ne so) non può essere fatto in nessun altro modo. Probabilmente ci sono altri modi per codificare questo, e probabilmente modi per ottimizzarlo, ma questo è fatto a mano e senza campane e fischietti per chiarezza per illustrare un uso di eval che in realtà non ha altre alternative. Cioè: nomi di oggetti dinamici (o più precisamente) creati in modo programmatico (in contrapposizione ai valori).
//Place this in a common/global JS lib:
var NS = function(namespace){
var namespaceParts = String(namespace).split(".");
var namespaceToTest = "";
for(var i = 0; i < namespaceParts.length; i++){
if(i === 0){
namespaceToTest = namespaceParts[i];
}
else{
namespaceToTest = namespaceToTest + "." + namespaceParts[i];
}
if(eval('typeof ' + namespaceToTest) === "undefined"){
eval(namespaceToTest + ' = {}');
}
}
return eval(namespace);
}
//Then, use this in your class definition libs:
NS('Root.Namespace').Class = function(settings){
//Class constructor code here
}
//some generic method:
Root.Namespace.Class.prototype.Method = function(args){
//Code goes here
//this.MyOtherMethod("foo")); // => "foo"
return true;
}
//Then, in your applications, use this to instantiate an instance of your class:
var anInstanceOfClass = new Root.Namespace.Class(settings);
EDIT: a proposito, non suggerirei (per tutti i motivi di sicurezza finora indicati) di basare i nomi degli oggetti sull'input dell'utente. Non riesco a immaginare nessuna buona ragione per cui vorresti farlo. Tuttavia, ho pensato di sottolineare che non sarebbe stata una buona idea :)
namespaceToTest[namespaceParts[i]]
, non c'è bisogno di eval qui, quindi if(typeof namespaceToTest[namespaceParts[i]] === 'undefined') { namespaceToTest[namespaceParts[i]] = {};
l'unica differenza perelse namespaceToTest = namespaceToTest[namespaceParts[i]];
Raccolta dei rifiuti
La garbage collection del browser non ha idea se il codice che viene valutato può essere rimosso dalla memoria, quindi lo mantiene solo fino a quando la pagina viene ricaricata. Non male se i tuoi utenti sono solo sulla tua pagina a breve, ma può essere un problema per webapp.
Ecco uno script per dimostrare il problema
https://jsfiddle.net/CynderRnAsh/qux1osnw/
document.getElementById("evalLeak").onclick = (e) => {
for(let x = 0; x < 100; x++) {
eval(x.toString());
}
};
Qualcosa di semplice come il codice sopra fa sì che una piccola quantità di memoria venga archiviata fino alla morte dell'app. Questo è peggio quando la sceneggiatura valutata è una funzione gigante e viene chiamata a intervalli.