Pergunta

Estou a escrever algum código JavaScript para analisar inseridos pelo usuário (funções da planilha-como funcionalidade).Tendo analisado a fórmula eu poderia convertê-lo em JavaScript e executá eval() para produzir o resultado.

No entanto, eu sempre esquivado de uso eval() se eu puder evitar isso, porque o mal (e, bem ou mal, eu sempre achei que é ainda mais mal em JavaScript, porque o código a ser avaliada pode ser alterado pelo usuário).

Assim, quando é OK para usá-lo?

Foi útil?

Solução

Eu gostaria de levar um momento para abordar a premissa da sua pergunta - que avaliar () é "mal". A palavra "mal", como usado pela programação de pessoas da linguagem, geralmente significa" perigoso ", ou mais precisamente" capaz de causar muitos danos com um comando de aparência simples ". Então, quando é bom usar algo perigoso? Quando você sabe o que o perigo é e quando você está tomando as precauções apropriadas.

Ao ponto, vejamos os perigos no uso de avaliar (). Provavelmente, existem muitos pequenos perigos ocultos, como todo o resto, mas os dois grandes riscos - a razão pela qual avaliar () é considerada má - são desempenho e injeção de código.

  • Desempenho - Eval () executa o intérprete/compilador. Se o seu código for compilado, esse é um grande sucesso, porque você precisa chamar um compilador possivelmente pesado no meio do tempo de execução. No entanto, o JavaScript ainda é principalmente uma linguagem interpretada, o que significa que chamar avaliar () não é um grande desempenho no caso geral (mas veja minhas observações específicas abaixo).
  • Injeção de código - Eval () potencialmente executa uma sequência de código sob privilégios elevados. Por exemplo, um programa em execução como administrador/root nunca gostaria de avaliar () a entrada do usuário, porque essa entrada poderia ser "rm -rf/etc/importante arquivo" ou pior. Novamente, o JavaScript em um navegador não tem esse problema, porque o programa está sendo executado na própria conta do usuário. O JavaScript do lado do servidor pode ter esse problema.

No seu caso específico. Pelo que eu entendo, você está gerando as cordas, portanto, assumindo que você tenha cuidado para não permitir que uma string como "rm -rf algo importante" seja gerada, não há risco de injeção de código (mas lembre -se, é Muito, muito difícil para garantir isso no caso geral). Além disso, se você estiver correndo no navegador, a injeção de código é um risco bastante pequeno, acredito.

Quanto ao desempenho, você terá que pesar isso contra a facilidade de codificação. É minha opinião que, se você estiver analisando a fórmula, também poderá calcular o resultado durante o parse em vez de executar outro analisador (aquele dentro de avaliação ()). Mas pode ser mais fácil codificar usando avaliar (), e o desempenho do desempenho provavelmente será imperceptível. Parece que avaliar () neste caso não é mais mau do que qualquer outra função que possa economizar algum tempo.

Outras dicas

eval() não é mau. Ou, se for, é mau da mesma maneira que a reflexão, a E/S de arquivos/rede, o encadeamento e o IPC são "maus" em outros idiomas.

Se, para o seu propósito, eval() é mais rápido que a interpretação manual ou torna seu código mais simples ou mais claro ... então você deve usá -lo. Se nenhum deles, então você não deveria. Simples assim.

Quando você confia na fonte.

No caso de JSON, é mais ou menos difícil adulterar a fonte, porque vem de um servidor da Web que você controla. Enquanto o próprio JSON não contiver dados que um usuário enviou, não há grande desvantagem para usar o aval.

Em todos os outros casos, eu faria um grande esforço para garantir que os dados fornecidos pelo usuário estejam em conformidade com minhas regras antes de alimentá -lo para avaliar ().

Vamos conseguir pessoal de verdade:

  1. Todo navegador importante agora tem um console embutido que seu possível hacker pode usar com abundância para invocar alguma função com algum valor - por que eles se incomodariam em usar uma declaração de avaliação - mesmo que pudesse?

  2. Se levar 0,2 segundos para compilar 2000 linhas de JavaScript, qual é a minha degradação de desempenho se eu avaliar quatro linhas de JSON?

Até a explicação de Crockford para 'avaliar é mal' é fraca.

Eval é mau, a função de avaliação é a característica mais usada do JavaScript. Evite isso

Como o próprio Crockford pode dizer "esse tipo de afirmação tende a gerar neurose irracional. Não compre".

Compreender avaliar e saber quando pode ser útil é muito mais importante. Por exemplo, o EVAL é uma ferramenta sensata para avaliar as respostas do servidor que foram geradas pelo seu software.

BTW: prototype.js Calls avaliar diretamente cinco vezes (inclusive em Evaljson () e avaliação ()). O JQuery o usa em Parsejson (via Functor Constructor).

Eu tendem a seguir O conselho de Crockford por eval(), e evite completamente. Mesmo maneiras que parecem exigir que não. Por exemplo, o setTimeout() Permite passar uma função em vez de avaliar.

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

Mesmo que seja um confiável Fonte, eu não o uso, porque o código retornado pelo JSON pode ser iluminado, o que poderia, na melhor das hipóteses, fazer algo instável, na pior das hipóteses, expor algo ruim.

A avaliação é complementar à compilação que é usada no modelo do código. Por modelos, quero dizer que você escreve um gerador de modelo simplificado que gera código de modelo útil que aumenta a velocidade de desenvolvimento.

Escrevi uma estrutura, onde os desenvolvedores não usam avaliar, mas eles usam nossa estrutura e, por sua vez, essa estrutura precisa usar o aval para gerar modelos.

O desempenho da avaliação pode ser aumentado usando o seguinte método; Em vez de executar o script, você deve retornar uma função.

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

Deve ser organizado como

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

var a = f(3,5);

O cache F certamente melhorará a velocidade.

Além disso, o Chrome permite a depuração de tais funções com muita facilidade.

Em relação à segurança, usar avaliar ou não dificilmente fará nenhuma diferença,

  1. Primeiro de tudo, o navegador chama todo o script em uma caixa de areia.
  2. Qualquer código que seja mau em avaliação é mau no próprio navegador. O invasor ou qualquer pessoa pode injetar facilmente um nó de script no DOM e fazer qualquer coisa se puder avaliar qualquer coisa. Não usar avaliar não fará nenhuma diferença.
  3. É principalmente uma segurança do lado do servidor que é prejudicial. Validação de biscoitos ruins ou implementação de baixa LCA no servidor causa a maioria dos ataques.
  4. Uma vulnerabilidade Java recente, etc. estava lá no código nativo de Java. O JavaScript foi e foi projetado para ser executado em uma caixa de areia, enquanto os applets foram projetados para correr fora de uma caixa de areia com certificados etc. que levam a vulnerabilidades e muitas outras coisas.
  5. Escrever código para imitar um navegador não é difícil. Tudo o que você precisa fazer é fazer uma solicitação HTTP ao servidor com sua sequência de agente de usuário favorita. De qualquer forma, todas as ferramentas de teste simulam navegadores; Se um invasor quiser prejudicá -lo, avaliar é o último recurso. Eles têm muitas outras maneiras de lidar com sua segurança do lado do servidor.
  6. O navegador DOM não tem acesso a arquivos e não é um nome de usuário. De fato, nada na máquina que a avaliação pode dar acesso.

Se a sua segurança do lado do servidor for sólida o suficiente para alguém atacar de qualquer lugar, você não deve se preocupar com a avaliação. Como mencionei, se a avaliação não existiria, os invasores terão muitas ferramentas para invadir seu servidor, independentemente do recurso de avaliação do seu navegador.

A avaliação é boa apenas para gerar alguns modelos para fazer processamento complexo de string com base em algo que não é usado com antecedência. Por exemplo, vou preferir

"FirstName + ' ' + LastName"

Em oposição a

"LastName + ' ' + FirstName"

Como o meu nome de exibição, que pode vir de um banco de dados e que não é codificado.

Eu vi as pessoas defenderem não usar avaliar, porque é mal, mas vi as mesmas pessoas usarem a função e o Settimeout dinamicamente, então elas usam avaliar sob os capuzes : D

BTW, se sua caixa de areia não tiver certeza (por exemplo, se você estiver trabalhando em um site que permita a injeção de código) é o último dos seus problemas. A regra básica de segurança é que tudo a entrada é má, mas no caso de JavaScript até O Javascript em si pode ser mau, porque em JavaScript você pode substituir qualquer função e simplesmente não pode ter certeza de que está usando o real, então, se um código malicioso começar antes de você, você não pode confiar em nenhum javascript embutido função: d

Agora o epílogo deste post é:

Se você VERDADE Precisa (80% das vezes avaliar é NÃO necessário) e você tem certeza do que está fazendo, basta usar Eval (ou Função melhor;)), fechamentos e OOP cobrem os 80/90% do caso em que avaliar pode ser substituído usando outro tipo de lógica, o resto é um código gerado dinamicamente (por exemplo, se você está escrevendo um intérprete) e como você já disse, avaliando o JSON (aqui você pode usar a avaliação segura de Crockford;))))

Quando a depuração no Chrome (v28.0.1500.72), achei que as variáveis não são associados aos vedantes se eles não são usados em uma função aninhada que produz o fechamento.Eu acho, que é uma otimização do motor de JavaScript.

MAS:quando eval() é utilizado dentro de uma função que faz com que um encerramento, TODOS as variáveis externas funções estão vinculados para o encerramento, mesmo se eles não são usados.Se alguém tem tempo para testar se fugas de memória podem ser produzidos por que, por favor me deixe um comentário abaixo.

Aqui está o meu código de teste:

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

O que eu gostaria de destacar aqui é que a função eval() não deve, necessariamente, referir-se ao nativo eval() função. Tudo depende do nome da função..Então, quando chamar os nativos eval() com um nome de alias (dizer var noval = eval; e, em seguida, em uma função interna noval(expression);), em seguida, a avaliação de expression pode falhar quando ele se refere às variáveis que devem fazer parte do seu fechamento, mas é, na verdade, não.

A Microsoft explica por que avaliar () é lento em seu navegador no blog do IE, IE+JavaScript Desempenho Recomendações Parte 2: Ineficiências de código JavaScript.

A única instância em que você deve usar o avaliar () é quando você precisa executar o JS dinâmico em tempo real. Estou falando de JS que você baixará de forma assíncrona do servidor ...

... e 9 vezes de 10, você pode facilmente evitar fazer isso refatorando.

Não há problema em usá -lo se você tiver controle completo sobre o código que foi passado para o eval função.

eval raramente é a escolha certa.Enquanto pode haver vários casos onde você pode realizar o que você precisa para realizar a concatenação de um script e executá-lo imediatamente, você normalmente tem muito mais poderoso e de fácil manutenção técnicas à sua disposição:associativa-a notação de matriz (obj["prop"] é o mesmo que obj.prop), vedantes, orientada a objetos, técnicas, funcionais, técnicas de usá - los em vez disso.

No que diz respeito ao script do cliente, acho que a questão da segurança é um ponto discutível. Tudo carregado no navegador está sujeito a manipulação e deve ser tratado como tal. Há riscos zero no uso de uma instrução Eval () quando existem maneiras muito mais fáceis de executar o código JavaScript e/ou manipular objetos no DOM, como a barra URL no seu navegador.

javascript:alert("hello");

Se alguém quer manipular seu DOM, eu digo fugir. A segurança para evitar qualquer tipo de ataque deve sempre ser de responsabilidade do aplicativo do servidor, período.

Do ponto de vista pragmático, não há benefício em usar um avaliar () em uma situação em que as coisas possam ser feitas de outra forma. No entanto, existem casos específicos em que uma avaliação deve ser usada. Quando assim, ele pode definitivamente ser feito sem nenhum risco de explodir a 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>

Quando JavaScript's Eval () não é mau?

Estou sempre tentando desencorajar de usar avaliar. Quase sempre, uma solução mais limpa e sustentável está disponível. Aval não é necessário mesmo para JSON Parsing. Aval adiciona ao inferno de manutenção. Não sem razão, é desaprovado por mestres como Douglas Crockford.

Mas eu encontrei um exemplo onde deveria estar usado:

Quando você precisa passar a expressão.

Por exemplo, tenho uma função que constrói um general google.maps.ImageMapType Objeta -me para mim, mas preciso dizer a receita, como ela deve construir o URL do ladrilho a partir do zoom e coord Parâmetros:

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

Meu exemplo de uso eval: importar.

Como geralmente é feito.

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

Mas com a ajuda de eval E uma pequena função auxiliar, tem uma aparência muito melhor:

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

importable Pode parecer (esta versão não suporta a importação de membros 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;
}

No servidor, a avaliação é útil ao lidar com scripts externos, como SQL ou InfluxDB ou Mongo. Onde a validação personalizada em tempo de execução pode ser feita sem re-implantar seus serviços.

Por exemplo, um serviço de conquista com os seguintes metadados

{
  "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 então permitem,

  • Injeção direta de objeto/valores através da string literal em um JSON, útil para textos de modelos

  • Pode ser usado como comparador, digamos que fazemos regras como validar missão ou eventos no CMS

CON disso:

  • Pode ser erros no código e dividir as coisas no serviço, se não for totalmente testado.

  • Se um hacker puder escrever um script no seu sistema, você estará praticamente ferrado.

  • Uma maneira de validar seu script é manter o hash dos seus scripts em algum lugar seguro, para que você possa verificá -los antes de executar.

Eu acho que qualquer caso de avaliação justificado seria raro. É mais provável que você use pensando que é justificado do que você para usá -lo quando é na realidade justificado.

Os problemas de segurança são os mais conhecidos. Mas também esteja ciente de que o JavaScript usa a compilação do JIT e isso funciona muito mal com a aval. A avaliação é um pouco como uma caixa preta para o compilador, e o JavaScript precisa ser capaz de prever o código antes do tempo (até certo ponto) para aplicar com segurança e correção otimizações e o escopo. Em alguns casos, o impacto do desempenho pode até afetar outro código fora da avaliação.

Se você quiser saber mais:https://github.com/getify/you-dont-know-js/blob/master/scope%20%26%20Closures/ch2.md#eval

Somente durante o teste, se possível. Observe também que avaliadores de avaliadores muito mais lentos do que outros JSON especializados etc..

Geração de código. Eu escrevi recentemente uma biblioteca chamada Hiperbar que preenche a lacuna entre Virtual-Dom e guidão. Isso faz isso analisando um modelo de guidão e convertendo -o para Hiperscrito. O hiperscrito é gerado como uma corda primeiro e antes de devolvê -la, eval() para transformá -lo em código executável. eu encontrei eval() Nesta situação em particular, exatamente o oposto do mal.

Basicamente de

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

Para isso

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

O desempenho de eval() Também não é um problema em uma situação como essa, porque você só precisa interpretar a string gerada uma vez e reutilizar a saída executável muitas vezes.

Você pode ver como a geração de código foi alcançada se você estiver curioso aqui.

Não há razão para não usar avaliar () desde que você possa ter certeza de que a fonte do código vem de você ou do usuário real. Mesmo que ele possa manipular o que é enviado para a função Eval (), isso não é um problema de segurança, porque ele é capaz de manipular o código -fonte do site e, portanto, poder alterar o próprio código JavaScript.

Então ... quando não usar avaliar ()? Eval () não deve ser usado apenas quando houver uma chance de que um terceiro possa alterá -lo. Como interceptar a conexão entre o cliente e seu servidor (mas se isso for um problema, use https). Você não deve avaliar () para analisar o código escrito por outras pessoas como em um fórum.

Se é realmente necessário avaliar não é mau. Mas 99,9% dos usos da avaliação que eu tropeço são não necessário (sem incluir coisas do settimeout).

Para mim, o mal não é uma performance ou mesmo uma questão de segurança (bem, indiretamente, são os dois). Todos esses usos desnecessários da avaliação adicionam a um inferno de manutenção. As ferramentas de refatoração são jogadas fora. Procurar código é difícil. Efeitos imprevistos desses evalos são Legião.

Minha crença é que o Eval é uma função muito poderosa para aplicativos da Web do lado do cliente e seguros ... tão seguros quanto o JavaScript, que não são. :-) Os problemas de segurança são essencialmente um problema do lado do servidor, porque, agora, com ferramentas como o Firebug, você pode atacar qualquer aplicativo JavaScript.

Eval é útil para a geração de código quando você não possui macros.

Para (um estúpido) exemplo, se você está escrevendo um Brainfuck Compilador, você provavelmente desejará construir uma função que execute a sequência de instruções como uma string e avaliá -la para retornar uma função.

Quando você analisa uma estrutura JSON com uma função Parse (por exemplo, jQuery.parsejson), espera uma estrutura perfeita do arquivo json (cada nome da propriedade está em cotações duplas). No entanto, o JavaScript é mais flexível. Portanto, você pode usar avaliar () para evitá -lo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top