Perché le caratteristiche eval-like sono considerate malvagie, in contrasto con altre caratteristiche potenzialmente dannose?

51

La maggior parte delle lingue moderne (che sono in qualche modo interpretate) hanno qualche tipo di funzione eval . Tale funzione esegue codice di linguaggio arbitrario, per la maggior parte del tempo passato come argomento principale come stringa (lingue diverse possono aggiungere più funzioni alla funzione eval).

Capisco che agli utenti non dovrebbe essere permesso di eseguire questa funzione ( modifica cioè di prendere direttamente o indirettamente input arbitrari da un utente arbitrario da passare a eval ), specialmente con il software lato server, dal momento che potrebbero forzare il processo per eseguire codice dannoso. In questo modo, tutorials e community ci dicono di non utilizzare eval. Tuttavia, ci sono molte volte in cui eval è utile e usato:

  • Regole di accesso personalizzate agli elementi software (IIRC OpenERP ha un oggetto ir.rule che può usare il codice python dinamico).
  • Calcoli e / o criteri personalizzati (OpenERP ha campi simili per consentire calcoli di codice personalizzati).
  • parser di report OpenERP (sì, lo so che ti sto facendo impazzire con le cose di OpenERP ... ma è l'esempio principale che ho)
  • Codifica gli effetti degli incantesimi in alcuni giochi di ruolo.

Quindi hanno un buon uso, a patto che vengano usati correttamente. Il vantaggio principale è che la funzione consente agli amministratori di scrivere codice personalizzato senza dover creare più file e includerli (sebbene la maggior parte dei framework che usano le caratteristiche di eval abbiano anche un modo per specificare un file, un modulo, un pacchetto, ... da leggere).

Tuttavia, eval è malvagio nella cultura popolare. Mi viene in mente qualcosa di simile al tuo sistema.

Tuttavia, ci sono altre funzioni che potrebbero essere dannose se in qualche modo accessibili dagli utenti: scollegare, leggere, scrivere (semantica del file), allocazione della memoria e aritmetica del puntatore, accesso al modello di database (anche se non si considerano casi iniettabili SQL). / p>

Quindi, in pratica, la maggior parte delle volte in cui qualsiasi codice non è scritto correttamente o non guardato correttamente (risorse, utenti, ambienti, ...), il codice è malvagio e può portare anche all'impatto economico.

Ma c'è qualcosa di speciale con le funzioni eval (indipendentemente dalla lingua).

Domanda : c'è qualche fatto storico per cui questa paura diventa parte della cultura popolare, invece di prestare la stessa attenzione alle altre caratteristiche potenzialmente pericolose?

    
posta Luis Masuelli 01.03.2016 - 17:56
fonte

13 risposte

76

Una funzione eval di per sé non è malvagia, e c'è un punto sottile che non credo che tu stia facendo:

Consentire a un programma di eseguire input arbitrari dell'utente è sbagliato

Ho scritto un codice che utilizzava un tipo di funzione eval ed era sicuro: il programma ei parametri erano codificati. A volte, non esiste una lingua o una funzione di libreria per fare ciò di cui ha bisogno il programma e l'esecuzione di un comando di shell è il percorso breve. "Devo terminare la codifica in poche ore, ma scrivere Java / .NET / PHP / qualunque codice impiegherà due giorni o posso eval in cinque minuti."

Una volta che permetti agli utenti di eseguire tutto ciò che vogliono, anche se bloccato per privilegio dell'utente o dietro una schermata "sicura", crei i vettori di attacco. Ogni settimana, alcuni CMS casuali, software di blogging, ecc. Hanno un buco di sicurezza patchato dove un aggressore può sfruttare un buco come questo. Stai facendo affidamento sull'intero stack software per proteggere l'accesso a una funzione che può essere utilizzata a rm -rf / o qualcos'altro catastrofico (nota: è improbabile che il comando abbia successo, ma fallirà dopo causando un po ' di danno).

Is there any historical fact for this fear becoming part of the popular culture, instead of putting the same attention to the other possibly dangerous features?

Sì, c'è un precedente storico. A causa dei numerosi bug che sono stati corretti negli anni in vari software che consentono agli aggressori remoti di eseguire codice arbitrario, l'idea di eval è in gran parte caduta dal favore. I linguaggi e le librerie moderne hanno un ricco set di funzionalità che rendono eval meno importante, e questo non è un caso. Rende entrambe le funzioni più facili da usare e riduce il rischio di un exploit.

C'è stata molta attenzione a molte funzionalità potenzialmente insicure nelle lingue popolari. Se ricevere più attenzione è principalmente una questione di opinione, ma le funzioni eval hanno certamente un problema di sicurezza dimostrabile che è facile da capire. Per uno, consentono l'esecuzione di comandi del sistema operativo inclusi i built-in della shell e i programmi esterni standard (ad esempio rm o del ). Due, in combinazione con altri exploit, un attaccante potrebbe essere in grado di caricare il proprio eseguibile o script di shell, quindi eseguirlo tramite il software, aprendo la porta a quasi qualsiasi cosa accada (nulla di buono).

Questo è un problema difficile. Il software è complesso e uno stack software (ad esempio LAMP ) è più pezzi di software che interagiscono tra loro in modi complessi. Fai attenzione a come utilizzi funzionalità linguistiche come questa e non consentire mai agli utenti di eseguire comandi arbitrari.

    
risposta data 01.03.2016 - 18:17
fonte
38

In parte è semplicemente che la sfumatura è difficile. È facile dire cosa come non usare mai goto, campi pubblici, interpolazione di stringhe per query SQL o eval. Queste affermazioni non dovrebbero essere intese nel senso che non c'è mai, in nessuna circostanza, una ragione per usarle. Ma evitarli come regola generale è una buona idea.

Eval è strongmente scoraggiato perché combina diversi problemi comuni.

In primo luogo, è suscettibile agli attacchi per iniezione. Qui è come l'iniezione SQL nel senso che quando i dati controllati dall'utente vengono inseriti nel codice, è facile che accidentalmente permetta l'inserimento di codice arbitrario.

In secondo luogo, i principianti tendono a usare eval per aggirare il codice mal strutturato. Un programmatore principiante potrebbe scrivere un codice simile a:

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

Funziona, ma è davvero il modo sbagliato per risolvere questo problema. Ovviamente, usare una lista sarebbe molto meglio.

In terzo luogo, l'eval è tipicamente inefficiente. Si spendono molti sforzi per accelerare le implementazioni del nostro linguaggio di programmazione. Ma la valutazione è difficile da accelerare e il suo utilizzo avrà in genere effetti negativi sulle prestazioni.

Quindi, la valutazione non è malvagia. Potremmo dire che la valutazione è malvagia, perché, è un modo accattivante per dirla. Qualunque programmatore principiante dovrebbe stare lontano dalla valutazione perché qualunque cosa vogliano fare, la valutazione è quasi certamente la soluzione sbagliata. Per alcuni casi d'uso avanzati, la valutazione ha senso, e dovresti usarla, ma ovviamente fai attenzione alle insidie.

    
risposta data 01.03.2016 - 18:19
fonte
20

Ciò che si riduce è che "l'esecuzione arbitraria di un codice" è un termine tecnico per "essere in grado di fare qualsiasi cosa". Se qualcuno è in grado di sfruttare l'esecuzione di codice arbitrario nel tuo codice, questa è letteralmente la peggiore vulnerabilità di sicurezza possibile, perché significa che sono in grado di fare tutto ciò che è possibile fare per il tuo sistema.

"Altri bug potenzialmente dannosi" potrebbero avere dei limiti, il che significa che sono, per definizione, in grado di causare meno danni di un'esecuzione arbitraria di codice che viene sfruttata.

    
risposta data 01.03.2016 - 18:11
fonte
15

C'è una ragione pratica e una ragione teorica.

La ragione pratica è che osserviamo che spesso causa problemi. È raro che la valutazione si traduca in una buona soluzione, e spesso si traduce in una cattiva soluzione in cui ne avresti una migliore alla fine se avessi preteso che la valuazione non esistesse e affrontato il problema in modo diverso. Quindi il consiglio semplificato è di ignorarlo, e se ti viene in mente un caso in cui vuoi ignorare il consiglio semplificato, beh, speriamo tu abbia riflettuto sufficientemente per capire perché il consiglio semplificato non è applicabile e il comune le insidie non ti influenzeranno.

La ragione più teorica è che se è difficile scrivere un buon codice, è ancora più difficile scrivere code che scrive un buon codice. Sia che tu stia utilizzando eval, sia che generi istruzioni SQL attaccando stringhe o scrivendo un compilatore JIT, ciò che stai tentando è spesso più difficile di quanto ti aspetti. Il potenziale per l'iniezione di codice dannoso è una gran parte del problema, ma a parte questo è in generale più difficile sapere se il codice è corretto se il tuo codice non esiste nemmeno fino al runtime. Quindi il consiglio semplificato è quello di rendere le cose più semplici: "usa query SQL parametrizzate", "non usare eval".

Esempio di effetti per incantesimi: una cosa è costruire un compilatore o interprete Lua (o qualsiasi altra cosa) nel tuo gioco per consentire ai game designer una lingua più semplice del C ++ (o qualsiasi altra cosa) per descrivere gli effetti degli incantesimi. La maggior parte dei "problemi di valutazione" non si applica se tutto quello che stai facendo è la valutazione del codice che è stato scritto e testato e incluso nel gioco o in DLC o what-have-you. Sta solo mescolando i linguaggi. I grandi problemi ti colpiscono quando provi a generare al volo Lua (o C ++, o SQL, o comandi della shell, o qualsiasi altra cosa), e rovinalo.

    
risposta data 01.03.2016 - 18:49
fonte
11

No, non c'è un fatto storico evidente.

I mali di eval sono semplici da vedere dall'inizio. Altre caratteristiche sono leggermente pericolose. Le persone possono cancellare i dati. Le persone possono vedere i dati che non dovrebbero Le persone possono scrivere dati che non dovrebbero. E possono solo fare la maggior parte di queste cose se in qualche modo sbagliate e non convalidate l'input dell'utente.

Con eval, possono hackerare il pentagono e farlo sembrare come l'hai fatto tu. Possono ispezionare le tue battiture per ottenere le tue password. Supponendo un linguaggio completo di Turing, possono fare letteralmente tutto ciò che il tuo computer è in grado di fare.

E non puoi convalidare l'input. È una stringa di forma libera arbitraria. L'unico modo per convalidarlo sarebbe creare un parser e un motore di analisi del codice per la lingua in questione. Buona fortuna con quello.

    
risposta data 01.03.2016 - 18:07
fonte
6

Penso che si riassuma nei seguenti aspetti:

  • Necessità
  • Uso (protetto)
  • Accesso
  • Verificabilità
  • Attacchi a più livelli

necessità

Hi there, I've written this extremely cool image editing tool (available for $ 0.02). After you have opened the image, you can pass a multitude of filters over your image. You can even script some yourself using Python (the program I've written the application in). I'll just use eval on your input, trusting you to be a respectable user.

(later)

Thanks for buying it. As you can see it functions exactly as I promised. Oh, You want to open an image? No, you can't. I won't use the read method as it is somewhat insecure. Saving? No, I won't use write.

Quello che sto cercando di dire è: hai bisogno di leggere / scrivere per quasi gli strumenti di base. Lo stesso vale per la memorizzazione dei punteggi più alti del tuo gioco oh-so-awesome.

Senza leggere / scrivere, il tuo editor di immagini è inutile. Senza eval ? Ti scriverò un plugin personalizzato per questo!

Uso (custodito)

Molti metodi possono essere potenzialmente pericolosi. Ad esempio, read e write . Un esempio comune è un servizio Web che consente di leggere le immagini da una directory specifica specificando il nome. Tuttavia, il "nome" può essere qualsiasi percorso (relativo) valido sul sistema, consentendo di leggere tutti i file a cui il servizio Web ha accesso, non solo le immagini. Abusare di questo semplice esempio si chiama 'path traversal'. Se la tua app ti consente di attraversare il percorso, è male. Un read senza difesa contro per il percorso trasversale può essere chiamato male.

Tuttavia, in altri casi, la stringa per read è completamente sotto il controllo del programmatore (forse codificato a macchina?). In tal caso, non è male usare read .

Accesso

Ora, un altro semplice esempio, usando eval .

Da qualche parte nella tua app web, vuoi un contenuto dinamico. Stai per consentire agli amministratori di inserire del codice che è eseguibile. Visto che gli amministratori sono utenti fidati, questo può teoricamente essere ok. Assicurati di non eseguire codice inviato da non amministratori e stai bene.

(Cioè, fino a quando non hai licenziato quel simpatico amministratore ma hai dimenticato di revocare il suo accesso. Ora la tua app web viene spostata nel cestino).

Verificabilità.

Un altro aspetto importante, penso, è la facilità con cui si verifica l'input dell'utente.

Utilizzo dell'input dell'utente in una chiamata letta? Basta fare (molto) sicuro che l'input per la chiamata di lettura non contenga nulla di malevolo. Normalizza il percorso e verifica che il file che viene aperto si trovi nella tua directory multimediale. Ora è sicuro.

Inserimento utente in una chiamata in scrittura? Stesso!

Iniezione SQL? Fuggi, o usa le query parametrizzate e sei al sicuro.

Eval? Come verificherete l'input utilizzato per la chiamata eval ? Puoi lavorare molto duramente, ma è davvero molto difficile (se non impossibile) farlo funzionare in modo sicuro.

Attacchi a più stadi

Ora, ogni volta che si utilizza l'input dell'utente, è necessario valutare i vantaggi di utilizzarlo, contro i pericoli. Proteggi il suo utilizzo il più possibile.

Considera di nuovo la roba eval in grado nell'esempio di amministrazione. Ti ho detto che era tutto ok.

Ora, considera che c'è effettivamente un posto nella tua app web in cui hai dimenticato di sfuggire al contenuto utente (HTML, XSS). Questo è un reato minore rispetto alla valutazione accessibile all'utente. Tuttavia, utilizzando il contenuto utente senza caratteri di escape, un utente può prendere il controllo del browser Web di un amministratore e aggiungere un blob in grado di eval attraverso la sessione degli amministratori, consentendo nuovamente l'accesso completo al sistema.

(Lo stesso attacco multistadio può essere eseguito con SQL injection anziché XSS oppure alcune scritture arbitrarie di file che sostituiscono il codice eseguibile invece di utilizzare eval )

    
risposta data 01.03.2016 - 19:16
fonte
3

Affinché questa funzionalità funzioni, significa che è necessario mantenere uno strato di riflessione intorno che consente il pieno accesso all'intero stato interno del programma.

Per le lingue interpretate, posso semplicemente usare lo stato dell'interprete, che è facile, ma in combinazione con i compilatori JIT aumenta ancora notevolmente la complessità.

Senza eval , il compilatore JIT può spesso dimostrare che i dati locali di un thread non sono accessibili da nessun altro codice, quindi è perfettamente accettabile riordinare gli accessi, omettere i blocchi e memorizzare i dati utilizzati più spesso per tempi più lunghi. Quando un altro thread esegue un'istruzione eval , potrebbe essere necessario sincronizzare il codice compilato JIT in esecuzione con quello, quindi improvvisamente il codice generato da JIT ha bisogno di un meccanismo di fallback che ritorni all'esecuzione non ottimizzata entro un intervallo di tempo ragionevole.

Questo tipo di codice tende ad avere molti bug sottili e difficili da riprodurre e allo stesso tempo pone un limite all'ottimizzazione nel compilatore JIT.

Per i linguaggi compilati, il compromesso è ancora peggiore: la maggior parte delle ottimizzazioni è proibita e ho bisogno di mantenere ampie informazioni sui simboli e un interprete in giro, quindi la flessibilità aggiuntiva non vale la pena, spesso è più facile definire un'interfaccia ad alcune strutture interne, ad es dando uno script visualizza e controller accesso concorrente al modello del programma.

    
risposta data 01.03.2016 - 23:40
fonte
1

Rifiuto la premessa che eval sia considerato più cattivo dell'aritmetica del puntatore o più pericoloso della memoria diretta e dell'accesso al file system. Non conosco nessuno sviluppatore ragionevole che ci creda. Inoltre, le lingue che supportano l'accesso aritmetico / diretto alla memoria puntatore di solito non supportano eval e viceversa, quindi sono sicuro che spesso tale confronto sarebbe rilevante.

Ma eval potrebbe essere una vulnerabilità più ben nota , per il semplice motivo che è supportata da JavaScript. JavaScript è un linguaggio sandbox senza memoria diretta o accesso al file system, quindi semplicemente non ha queste vulnerabilità che escludono punti deboli nell'implementazione del linguaggio stesso. Eval è quindi una delle caratteristiche più pericolose del linguaggio, poiché apre la possibilità di esecuzione di codice arbitrario. Credo che molti sviluppatori sviluppino in JavaScript piuttosto che in C / C ++, quindi eval è semplicemente più importante da conoscere rispetto ai buffer overflow per la maggior parte degli sviluppatori.

    
risposta data 02.03.2016 - 13:31
fonte
0

Nessun programmatore serio considererebbe Eval come "malvagio". È semplicemente uno strumento di programmazione, come qualsiasi altro. La paura (se c'è una paura) di questa funzione non ha nulla a che fare con la cultura popolare. È semplicemente un comando pericoloso che viene spesso utilizzato in modo improprio e può introdurre seri problemi di sicurezza e degradare le prestazioni. Dalla mia esperienza personale direi che è raro incontrare un problema di programmazione che non può essere risolto in modo più sicuro ed efficiente con altri mezzi. I programmatori che hanno maggiori probabilità di utilizzare eval, sono quelli che sono probabilmente meno qualificati per farlo in modo sicuro.

Detto questo, ci sono lingue in cui l'uso di eval è appropriato. Perl viene in mente. Tuttavia, personalmente ritengo che sia molto raramente necessario in altri linguaggi più moderni, che supportano nativamente la gestione delle eccezioni strutturata.

    
risposta data 02.03.2016 - 08:08
fonte
0

Penso che lo copra abbastanza bene nella parte successiva della tua domanda (enfasi mia):

they have a good use, as long as they are used properly

Per il 95% di chi potrebbe usarlo correttamente va tutto bene; ma ci saranno sempre persone che non lo usano correttamente. Alcuni di essi saranno inesperti e privi di capacità, il resto sarà malizioso.

Ci saranno sempre persone che vogliono spingere i confini e trovare falle nella sicurezza - alcune per sempre, altre per cattive.

Per quanto riguarda l'aspetto dei fatti storici, le funzioni di tipo eval consentono essenzialmente Esecuzione di codice arbitrario che è stata precedentemente sfruttato nel famoso CMS web, Joomla! . Con Joomla! che alimenta oltre 2,5 milioni di siti in tutto il mondo , questo è un potenziale danno non solo per i visitatori di quei siti, ma potenzialmente per l'infrastruttura su cui è ospitato e anche per la reputazione dei siti / aziende che sono stati sfruttati.

Il Joomla! esempio potrebbe essere semplice, ma è uno che è stato registrato.

    
risposta data 02.03.2016 - 11:10
fonte
0

Ci sono alcuni buoni motivi per scoraggiare l'uso di eval (anche se alcuni sono specifici per certe lingue).

  • L'ambiente (lessicalmente e dinamicamente) utilizzato da eval è spesso sorprendente (ovvero, pensi che eval(something here) dovrebbe fare una cosa, ma ne fa un'altra, probabilmente innescando eccezioni)
  • C'è spesso un modo migliore per realizzare la stessa cosa (la concatenazione di chiusure lessicali costruite è a volte una soluzione migliore, ma potrebbe essere specifica per Common Lisp)
  • È fin troppo facile finire con la valutazione di dati non sicuri (anche se questo può essere per lo più protetto).

Personalmente, non intendo dire che è malvagio, ma sfiderò sempre l'uso di eval in una revisione del codice, con le parole sulla linea di "hai preso in considerazione qualche codice qui ?" (se ho tempo per fare almeno un tentativo di sostituzione), o "sei sicuro che una valutazione è davvero la soluzione migliore qui?" (se non lo faccio).

    
risposta data 02.03.2016 - 12:55
fonte
0

In Minsky Society of Mind , Capitolo 6.4, dice

There is one way for a mind to watch itself and still keep track of what's happening. Divide the brain into two parts, A and B. Connect the A-brain's inputs and outputs to the real world - so it can sense what happens there. But don't connect the B-brain to the outer world at all; instead connect it so that the A-brain is the B-brain's world!

Quando un programma (B) scrive un altro programma (A) funziona come meta-programma. L'argomento di B è il programma A.

C'è un programma C. Quello è quello in il tuo capo che scrive il programma B.

Il pericolo è che ci può essere qualcuno (C ') con cattive intenzioni, che può interferire in questo processo, quindi ottieni cose come l'iniezione SQL.

La sfida è rendere il software più intelligente senza renderlo più pericoloso.

    
risposta data 02.03.2016 - 14:26
fonte
0

Hai molte buone risposte qui e il motivo principale è chiaramente che l'esecuzione arbitraria di codice è cattiva mmmkay ma aggiungerò un altro fattore che altri hanno solo sfiorato:

È veramente difficile risolvere il codice che viene valutato da una stringa di testo. I tuoi normali strumenti di debug sono praticamente fuori dalla finestra e ti ritrovi a tracciare o echi della vecchia scuola. Ovviamente non importa tanto se si ha un frammento in un one-liner che si vuole eval ma come programmatore esperto probabilmente si può trovare una soluzione migliore per questo, mentre una generazione di codice su larga scala può essere da qualche parte che una funzione di valutazione diventa un alleato potenzialmente utile.

    
risposta data 03.03.2016 - 13:37
fonte

Leggi altre domande sui tag