Por que os recursos parecidos com eval são considerados maus, em contraste com outros recursos possivelmente nocivos?

51

A maioria das linguagens modernas (que são de alguma forma interpretadas) tem algum tipo de função eval . Essa função executa um código de linguagem arbitrário, na maior parte do tempo passado como o argumento principal como uma string (diferentes idiomas podem adicionar mais recursos à função eval).

Eu entendo que os usuários não devem ter permissão para executar esta função ( editar , ou seja, usar entradas arbitrárias direta ou indiretamente de um usuário arbitrário para ser passado para eval ), especialmente com software do lado servidor já que eles poderiam forçar o processo a executar código malicioso. Dessa forma, tutoriais e comunidades nos dizem para não usar o eval. No entanto, há muitas vezes em que eval é útil e usado:

  • Regras de acesso personalizadas para elementos de software (o IIRC OpenERP tem um objeto ir.rule que pode usar código python dinâmico).
  • Cálculos personalizados e / ou critérios (OpenERP tem campos como esse para permitir cálculos de códigos personalizados).
  • Analisadores de relatórios do OpenERP (sim, eu sei que estou enlouquecendo com coisas do OpenERP ... mas é o principal exemplo que tenho).
  • Codificando efeitos de feitiços em alguns jogos de RPG.

Para que eles tenham um bom uso, contanto que sejam usados adequadamente. A principal vantagem é que o recurso permite que os administradores escrevam código personalizado sem precisar criar mais arquivos e incluí-los (embora a maioria das estruturas que usam recursos eval também tenham uma maneira de especificar um arquivo, módulo, pacote, ... para ler).

No entanto, eval é mal na cultura popular. Coisas como invadir seu sistema vêm à mente.

No entanto, há outras funções que podem ser prejudiciais se acessadas de alguma forma pelos usuários: desvincular, ler, escrever (semântica de arquivos), alocação de memória e aritmética de ponteiros, acesso ao modelo de banco de dados (mesmo não considerando casos de injeção de SQL). / p>

Então, basicamente, na maioria das vezes quando qualquer código não está escrito corretamente ou não assistiu corretamente (recursos, usuários, ambientes, ...), o código é mal e pode levar até ao impacto econômico.

Mas há algo especial com eval funções (independentemente do idioma).

Pergunta : Existe algum fato histórico para este medo se tornar parte da cultura popular, em vez de dar a mesma atenção aos outros recursos possivelmente perigosos?

    
por Luis Masuelli 01.03.2016 / 17:56
fonte

13 respostas

76

Uma função eval por si só não é mal, e há um ponto sutil que eu não acredito que você esteja fazendo:

Permitir que um programa execute entrada arbitrária do usuário é ruim

Eu escrevi código que usava um tipo de função eval e era seguro: o programa e os parâmetros eram codificados. Às vezes, não há recurso de linguagem ou biblioteca para fazer o que o programa precisa e executar um comando shell é o caminho curto. "Eu tenho que terminar de codificar isso em algumas horas, mas escrever Java / .NET / PHP / qualquer código levará dois dias. Ou eu posso eval em cinco minutos."

Depois de permitir que os usuários executem o que quiserem, mesmo que estejam bloqueados por privilégio de usuário ou por trás de uma tela "segura", você cria vetores de ataque. Toda semana, algum CMS aleatório, software de blog, etc. tem uma falha de segurança corrigida onde um atacante pode explorar um buraco como este. Você está contando com toda a pilha de software para proteger o acesso a uma função que pode ser usada para rm -rf / ou outra coisa catastrófica (note que é improvável que o comando tenha sucesso, mas falhará depois de causar um pouco de dano).

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?

Sim, existe um precedente histórico. Devido aos inúmeros erros que foram corrigidos ao longo dos anos em vários softwares que permitem que atacantes remotos executem códigos arbitrários, a idéia de eval caiu em desuso. Bibliotecas e linguagens modernas têm conjuntos ricos de funcionalidades que tornam eval menos importante, e isso não é por acaso. Isso torna as funções mais fáceis de usar e reduz o risco de uma exploração.

Tem havido muita atenção em muitos recursos potencialmente inseguros em idiomas populares. Se alguém recebe mais atenção é principalmente uma questão de opinião, mas os recursos eval certamente têm um problema de segurança que é fácil de entender. Por um lado, eles permitem a execução de comandos do sistema operacional, incluindo built-ins de shell e programas externos que são padrão (por exemplo, rm ou del ). Dois, combinados com outras façanhas, um invasor pode carregar seu próprio script executável ou shell e executá-lo através de seu software, abrindo a porta para que quase tudo aconteça (nada disso é bom).

Este é um problema difícil. O software é complexo e uma pilha de software (por exemplo, LAMP ) é várias peças de software que interagem entre si de formas complexas. Tenha cuidado ao usar recursos de linguagem como esse e nunca permita que os usuários executem comandos arbitrários.

    
por 01.03.2016 / 18:17
fonte
38

Parte disso é simplesmente que a nuance é difícil. É fácil dizer coisa como nunca usar goto, campos públicos, interpolação de strings para consultas sql ou eval. Estas declarações não devem ser entendidas como dizendo que nunca há, em nenhuma circunstância, uma razão para usá-las. Mas evitá-los como regra geral é uma boa ideia.

O Eval é altamente desencorajado porque combina vários problemas comuns.

Em primeiro lugar, é suscetível a ataques de injeção. Aqui é como a injeção de SQL em que quando dados controlados pelo usuário são inseridos no código, é fácil acidentalmente permitir a inserção de código arbitrário.

Em segundo lugar, os iniciantes tendem a usar o eval para contornar códigos mal estruturados. Um codificador iniciante pode escrever um código parecido com:

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

Isso funciona, mas é realmente o caminho errado para resolver esse problema. Obviamente, usar uma lista seria muito melhor.

Em terceiro lugar, o eval é tipicamente ineficiente. Muito esforço é gasto acelerando nossas implementações de linguagem de programação. Mas o eval é difícil de acelerar e usá-lo normalmente terá efeitos prejudiciais no seu desempenho.

Então, eval não é mal. Podemos dizer que eval é mal, porque, bem, é uma maneira cativante de colocar isso. Qualquer programador iniciante deve ficar estritamente longe do eval porque, seja o que for que ele queira fazer, o eval é quase certamente a solução errada. Para certos casos de uso avançado, eval faz sentido, e você deve usá-lo, mas obviamente tenha cuidado com as armadilhas.

    
por 01.03.2016 / 18:19
fonte
20
O que se resume a isso é que a "execução de código arbitrário" é uma tecnologia para "poder fazer qualquer coisa". Se alguém é capaz de explorar a execução arbitrária de código no seu código, esta é literalmente a pior vulnerabilidade de segurança possível, porque significa que eles são capazes de fazer tudo o que for possível para o seu sistema.

"Outros bugs possivelmente perigosos" podem ter limites, o que significa que eles são, por definição, capazes de causar menos danos do que uma execução de código arbitrário sendo explorada.

    
por 01.03.2016 / 18:11
fonte
15

Existe uma razão prática e teórica.

A razão prática é que nós observamos freqüentemente causa problemas. É raro que o eval resulte em uma boa solução, e geralmente resulta em uma solução ruim em que você teria um melhor caso, no final, você fingisse que o eval não existia e abordasse o problema de maneira diferente. Então o conselho simplificado é ignorá-lo, e se você chegar a um caso em que você queira ignorar o conselho simplificado, bem, vamos torcer para que você tenha pensado o suficiente para entender por que o conselho simplificado não é aplicável e o comum armadilhas não afetarão você.

A razão mais teórica é que, se é difícil escrever um código bom, é ainda mais difícil escrever código que escreve um código bom. Não importa se você está usando eval ou gerando instruções SQL unindo strings ou escrevendo um compilador JIT, o que você está tentando é mais difícil do que o esperado. O potencial de injeção de código mal-intencionado é uma grande parte do problema, mas, além disso, em geral, é mais difícil saber se o código está correto se o código não existir até o tempo de execução. Portanto, o conselho simplificado é manter as coisas mais fáceis para você: "use consultas SQL parametrizadas", "não use eval".

Tomando seu exemplo de efeitos de magia: uma coisa é construir um compilador ou interpretador de Lua (ou qualquer outro) em seu jogo para permitir aos designers de jogos uma linguagem mais fácil que C ++ (ou qualquer outra) para descrever efeitos de magia. A maioria dos "problemas de eval" não se aplica se tudo o que você está fazendo é avaliar o código que foi escrito e testado e incluído no jogo ou no DLC ou o que você tem. Isso é apenas misturar idiomas. Os grandes problemas acontecem quando você tenta gerar Lua (ou C ++, ou SQL, ou comandos shell, ou qualquer outro) na hora, e bagunça tudo.

    
por 01.03.2016 / 18:49
fonte
11

Não, não há nenhum fato histórico óbvio.

Os males da eval são fáceis de ver desde o começo. Outras características são levemente perigosas. As pessoas podem excluir dados. As pessoas podem ver os dados que não deveriam. As pessoas podem escrever dados que não deveriam. E eles só podem fazer a maioria dessas coisas se você de alguma forma estragar e não validar a entrada do usuário.

Com o eval, eles podem hackear o pentágono e fazer parecer que você fez isso. Eles podem inspecionar suas teclas para obter suas senhas. Assumindo uma linguagem completa de Turing, eles podem literalmente fazer qualquer coisa que seu computador seja capaz de fazer.

E você não pode validar a entrada. É uma string arbitrária de forma livre. A única maneira de validá-lo seria criar um analisador e um mecanismo de análise de código para o idioma em questão. Boa sorte com isso.

    
por 01.03.2016 / 18:07
fonte
6

Acho que se resume aos seguintes aspectos:

  • Necessidade
  • Uso
  • (protegido)
  • Acessar
  • Verificabilidade
  • Ataques em vários estágios

Necessidade

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.

O que estou tentando dizer é: você precisa ler / escrever para quase todas as ferramentas básicas. O mesmo para armazenar as pontuações mais altas do seu jogo incrível.

Sem leitura / gravação, o seu editor de imagens é inútil. Sem eval ? Vou escrever um plugin personalizado para isso!

Uso (protegido)

Muitos métodos podem ser perigosos. Como, por exemplo, o read e write . Um exemplo comum é um serviço da Web que permite ler imagens de um diretório específico, especificando o nome. No entanto, o 'nome' pode, de fato, ser qualquer caminho (relativo) válido no sistema, permitindo que você leia todos os arquivos aos quais o serviço da Web tem acesso, não apenas as imagens. O uso desse exemplo simples é chamado de "caminho percorrido". Se o seu aplicativo permitir a passagem de caminho, isso é ruim. Um read sem defesa contra a travessia do caminho pode ser chamado de mal.

No entanto, em outros casos, a string para read está totalmente sob controle dos programadores (talvez codificada?). Nesse caso, dificilmente é mal usar read .

Acesso

Agora, outro exemplo simples, usando eval .

Em algum lugar do seu aplicativo da Web, você deseja algum conteúdo dinâmico. Você permitirá que os administradores insiram um código que seja executável. Como os administradores são usuários confiáveis, isso pode, teoricamente, ser ok. Apenas certifique-se de não executar o código enviado por não administradores, e você está bem.

(Isto é, até que você tenha demitido aquele administrador legal, mas tenha esquecido de revogar o acesso dele. Agora seu aplicativo da Web está na lixeira).

Verificabilidade.

Outro aspecto importante, eu acho, é como é fácil verificar a entrada do usuário.

Usando a entrada do usuário em uma chamada de leitura? Apenas certifique (muito) de que a entrada para a chamada de leitura não contém nada malicioso. Normalize o caminho e verifique se o arquivo aberto está no diretório de mídia. Agora isso é seguro.

Entrada do usuário em uma chamada de gravação? O mesmo!

Injeção de SQL? Apenas escape, ou use consultas parametrizadas e você estará seguro.

Eval? Como você vai verificar a entrada usada para a chamada eval ? Você pode trabalhar muito, mas é muito difícil (se não impossível) fazer com que funcione com segurança.

Ataques em vários estágios

Agora, toda vez que você usar a entrada do usuário, precisará ponderar os benefícios de usá-la, contra os perigos. Proteja seu uso o máximo que puder.

Considere novamente o material eval able no exemplo de administrador. Eu te disse que era meio que ok.

Agora, considere que há realmente um lugar em seu aplicativo da web em que você esqueceu de escapar do conteúdo do usuário (HTML, XSS). Isso é menos ofensivo que o eval acessível ao usuário. Mas, usando o conteúdo do usuário sem escape, um usuário pode assumir o controle do navegador da Web de um administrador e adicionar um% de blob compatível com o código através da sessão do administrador, permitindo novamente o acesso total ao sistema.

(O mesmo ataque em vários estágios pode ser feito com injeção de SQL em vez de XSS, ou algumas gravações de arquivos arbitrárias substituindo o código executável em vez de usar eval )

    
por 01.03.2016 / 19:16
fonte
3

Para que esse recurso funcione, significa que preciso manter uma camada de reflexão que permita acesso total ao estado interno de todo o programa.

Para linguagens interpretadas, posso simplesmente usar o estado do interpretador, o que é fácil, mas em combinação com os compiladores JIT, ele ainda aumenta significativamente a complexidade.

Sem eval , o compilador JIT pode frequentemente provar que os dados locais de um encadeamento não são acessados de nenhum outro código, portanto é perfeitamente aceitável reordenar acessos, omitir bloqueios e armazenar em cache dados mais usados por mais tempo. Quando outro thread executa uma instrução eval , pode ser necessário sincronizar o código JIT compilado com ele, então, de repente, o código gerado pelo JIT precisa de um mecanismo de fallback que retorne à execução não otimizada dentro de um período de tempo razoável.

Esse tipo de código tende a ter muitos erros sutis e difíceis de reproduzir e, ao mesmo tempo, também coloca um limite na otimização do compilador JIT.

Para linguagens compiladas, a desvantagem é ainda pior: a maioria das otimizações é proibida, e eu preciso manter informações extensivas de símbolos e um interpretador, então a flexibilidade adicional geralmente não vale a pena - é mais fácil definir uma interface para algumas estruturas internas, por exemplo dando um acesso em script e controller acesso simultâneo ao modelo do programa.

    
por 01.03.2016 / 23:40
fonte
1

Eu rejeito a premissa de que eval é considerado mais mal do que a aritmética de ponteiro ou mais perigoso que a memória direta e o acesso ao sistema de arquivos. Não conheço nenhum desenvolvedor sensato que acreditasse nisso. Além disso, os idiomas que suportam acesso direto à memória aritmética / ponteiro normalmente não suportam eval e vice-versa, então estou certo de quantas vezes essa comparação seria relevante.

Mas eval pode ser uma vulnerabilidade mais bem conhecida , pelo simples motivo de ser suportada pelo JavaScript. O JavaScript é uma linguagem de área restrita sem memória direta ou acesso ao sistema de arquivos, portanto, simplesmente não tem essas vulnerabilidades bloqueando os pontos fracos na própria implementação da linguagem. O Eval é, portanto, uma das características mais perigosas da linguagem, já que abre a possibilidade de execução de código arbitrário. Acredito que muito mais desenvolvedores desenvolvem em JavaScript do que em C / C ++, portanto eval é simplesmente mais importante do que o buffer overflow para a maioria dos desenvolvedores.

    
por 02.03.2016 / 13:31
fonte
0

Nenhum programador sério consideraria Eval como "Mal". É simplesmente uma ferramenta de programação, como qualquer outra. O medo (se houver medo) dessa função não tem nada a ver com a cultura popular. É simplesmente um comando perigoso, que geralmente é mal utilizado, e pode apresentar falhas graves de segurança e degradar o desempenho. De minha própria experiência, eu diria que é raro encontrar um problema de programação que não possa ser resolvido de forma mais segura e eficiente por outros meios. Os programadores que têm maior probabilidade de usar o eval são aqueles que provavelmente são menos qualificados para fazê-lo com segurança.

Dito isto, há línguas em que o uso de eval é apropriado. Perl vem à mente. No entanto, eu pessoalmente acho que é muito raramente necessário em outras linguagens mais modernas, que suportam nativamente o tratamento estruturado de exceções.

    
por 02.03.2016 / 08:08
fonte
0

Acho que você o cobre bastante bem na parte seguinte da sua pergunta (ênfase minha):

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

Para os 95% que podem usá-lo corretamente, tudo está bem; mas sempre haverá pessoas que não o usam corretamente. Alguns deles se resumem a inexperiência e falta de habilidade, o resto será malicioso.

Sempre haverá pessoas que querem ultrapassar barreiras e encontrar falhas de segurança - algumas para o bem, outras para o mal.

Quanto ao aspecto do fato histórico, eval type funções permitem essencialmente Execução de código arbitrário que foi anteriormente explorado na popular web CMS, Joomla! . Com Joomla! Com mais de 2,5 milhões de sites em todo o mundo , há muitos danos em potencial não apenas para os visitantes desses sites, mas também para a infraestrutura em que estão hospedados e também para a reputação dos sites / empresas que foram explorados.

O Joomla! exemplo pode ser simples, mas é um que foi gravado.

    
por 02.03.2016 / 11:10
fonte
0

Existem algumas boas razões para desencorajar o uso de eval (embora algumas sejam específicas para determinados idiomas).

  • O ambiente (lexical e dinamicamente) usado por eval é freqüentemente surpreendente (isto é, você acha que eval(something here) deve fazer uma coisa, mas faz outra, possivelmente provocando exceções)
  • Frequentemente há uma maneira melhor de realizar a mesma coisa (a concatenação de fechamentos lexicais construídos às vezes é uma solução melhor, mas isso pode muito bem ser específico do Common Lisp)
  • É muito fácil acabar com dados inseguros sendo avaliados (embora isso possa ser protegido contra a maioria).

Eu, pessoalmente, não chego a dizer que isso é ruim, mas sempre vou desafiar o uso de eval em uma revisão de código, com formulações ao longo da linha de "você já considerou algum código aqui ? " (se eu tiver tempo de fazer pelo menos uma tentativa de substituição), ou "você tem certeza de que uma avaliação realmente é a melhor solução aqui?" (se eu não).

    
por 02.03.2016 / 12:55
fonte
0

Na Sociedade da Mente de Minsky, no capítulo 6.4, ele diz

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 um programa (B) escreve outro programa (A), ele está funcionando como metaprograma. O assunto de B é o programa A.

Existe um programa em C. Essa é a da sua cabeça que escreve o programa B.

O perigo é que pode haver alguém (C ') com má intenção, que pode interferir nesse processo, então você recebe coisas como injeção de SQL.

O desafio é tornar o software mais inteligente sem também torná-lo mais perigoso.

    
por 02.03.2016 / 14:26
fonte
0

Você tem muitas boas respostas aqui e a principal razão é claramente que a execução arbitrária de código é ruim mmmkay mas adicionarei um outro fator que os outros tocaram apenas no limite:

É realmente difícil solucionar problemas de código que está sendo avaliado fora de uma string de texto. Suas ferramentas normais de depuração estão praticamente em linha reta fora da janela e você está reduzido a traçar ou ecoar muito da velha escola. Obviamente, isso não importa muito se você tem um fragmento em uma linha que você quer eval , mas como um programador experiente você provavelmente encontrará uma solução melhor para isso, enquanto a geração de código em larga escala pode estar em algum lugar uma função de avaliação torna-se um aliado potencialmente útil.

    
por 03.03.2016 / 13:37
fonte