Verifique se o código não seguro não é usado acidentalmente

10

Uma função f()usa eval()(ou algo tão perigoso) com os dados que eu criei e armazenei na local_filemáquina executando meu programa:

import local_file

def f(str_to_eval):
    # code....
    # .... 
    eval(str_to_eval)    
    # ....
    # ....    
    return None


a = f(local_file.some_str)

f() é seguro de executar, pois as cordas que forneço são minhas.

No entanto, se eu decidir usá-lo para algo inseguro (por exemplo, entrada do usuário), as coisas podem dar muito errado . Além disso, se o local_filelocal deixar de ser local, isso criaria uma vulnerabilidade, pois eu precisaria confiar na máquina que fornece esse arquivo também.

Como devo garantir que nunca "esqueço" que esta função não é segura (a menos que critérios específicos sejam atendidos)?

Nota: eval()é perigoso e geralmente pode ser substituído por algo seguro.

Paradoxo de Fermi
fonte
1
Normalmente, gosto de acompanhar as perguntas feitas, em vez de dizer "você não deve fazer isso", mas vou abrir uma exceção nesse caso. Estou certo de que não há razão para você usar eval. Essas funções estão incluídas em linguagens como PHP e Python como uma espécie de prova de conceito quanto pode ser feito com linguagens interpretadas. É fundamentalmente inconcebível que você possa escrever código que crie código, mas que não possa codificar a mesma lógica em uma função. Python provavelmente tem retornos de chamada e ligações de função que lhe permitirá fazer o que você precisa com tão
user1122069
1
" Eu precisaria confiar na máquina que fornece esse arquivo também " - você sempre precisa confiar na máquina em que está executando seu código.
Bergi 4/03/16
Coloque um comentário no arquivo dizendo "Esta string é avaliada; por favor, não coloque código malicioso aqui"?
user253751
@immibis Um comentário não seria suficiente. Obviamente, terei um enorme "AVISO: Esta função usa eval ()" nos documentos da função, mas prefiro confiar em algo muito mais seguro do que isso. Por exemplo (devido ao tempo limitado) nem sempre estou relendo os documentos de uma função. Nem eu acho que deveria estar. Existem maneiras mais rápidas (ou seja, mais eficientes) de saber que algo é inseguro, como os métodos vinculados por Murphy em sua resposta.
Paradoxo de Fermi 04/03
@Fermiparadox quando você tira a avaliação da pergunta, ela se torna sem exemplo útil. Você está falando agora de uma função que é insegura por causa de uma supervisão deliberada, como não escapar dos parâmetros SQL. Você quer dizer que o código de teste não é seguro para um ambiente de produção? Enfim, agora eu li seu comentário sobre a resposta de Amon - basicamente você estava procurando como proteger sua função.
precisa saber é o seguinte

Respostas:

26

Defina um tipo para decorar entradas "seguras". Na sua função f(), afirme que o argumento tem esse tipo ou gere um erro. Seja esperto o suficiente para nunca definir um atalho def g(x): return f(SafeInput(x)).

Exemplo:

class SafeInput(object):
  def __init__(self, value):
    self._value = value

  def value(self):
    return self._value

def f(safe_string_to_eval):
  assert type(safe_string_to_eval) is SafeInput, "safe_string_to_eval must have type SafeInput, but was {}".format(type(safe_string_to_eval))
  ...
  eval(safe_string_to_eval.value())
  ...

a = f(SafeInput(local_file.some_str))

Embora isso possa ser facilmente contornado, torna mais difícil fazê-lo por acidente. As pessoas podem criticar esse tipo de verificação draconiana, mas não entenderiam o ponto. Como o SafeInputtipo é apenas um tipo de dados e não uma classe orientada a objetos que pode ser subclassificada, verificar a identidade do tipo com type(x) is SafeInputé mais seguro do que verificar a compatibilidade do tipo isinstance(x, SafeInput). Como queremos aplicar um tipo específico, ignorar o tipo explícito e apenas fazer a digitação implícita do pato também não é satisfatório aqui. O Python possui um sistema de tipos, então vamos usá-lo para detectar possíveis erros!

amon
fonte
5
Uma pequena alteração pode ser necessária. asserté removido automaticamente, pois usarei código python otimizado. Algo como if ... : raise NotSafeInputErrorseria necessário.
Fermi paradox
4
Se você estiver seguindo esse caminho de qualquer maneira, eu recomendo fortemente que você mude eval()para um método SafeInput, para que você não precise de uma verificação explícita de tipo. Dê ao método um nome que não seja usado em nenhuma outra classe. Dessa forma, você ainda pode zombar da coisa toda para fins de teste.
Kevin
@ Kevin: Visto que evaltem acesso a variáveis ​​locais, pode ser que o código dependa de como as variáveis ​​são modificadas. Ao mover o eval para outro método, ele não terá mais a capacidade de alterar variáveis ​​locais.
Sjoerd Job Postmus
Uma etapa adicional pode ser fazer com que o SafeInputconstrutor pegue um nome / identificador do arquivo local e faça a própria leitura, garantindo que os dados realmente venham de um arquivo local.
Owen
1
Eu não tenho muita experiência com python, mas não é melhor lançar uma exceção? Na maioria dos idiomas, as declarações podem ser desativadas e são (como eu as entendo) mais para depuração do que para segurança. Exceções e saídas ao console garantem que o próximo código não será executado.
precisa saber é o seguinte
11

Como Joel apontou certa vez, faça com que o código errado pareça errado (role até a seção "A solução real", ou melhor, leia-o completamente; vale a pena, mesmo que ele endereça variáveis ​​ao invés de nomes de funções). Então, humildemente, sugiro renomear sua função para algo como f_unsafe_for_external_use()- você provavelmente criará algum esquema de abreviação.

Edit: Eu acho que a sugestão de amon é uma variação muito boa, baseada em OO, sobre o mesmo tema :-)

Murphy
fonte
0

Complementando a sugestão de @amon, você também pode pensar em combinar o padrão de estratégia aqui, bem como o padrão de objeto do método.

class AbstractFoobarizer(object):
    def handle_cond(self, foo, bar):
        """
        Handle the event or something.
        """
        raise NotImplementedError

    def run(self):
        # do some magic
        pass

E então uma implementação 'normal'

class PrintingFoobarizer(AbstractFoobarizer):
    def handle_cond(self, foo, bar):
        print("Something went very well regarding {} and {}".format(foo, bar))

E uma implementação admin-input-eval

class AdminControllableFoobarizer(AbstractFoobarizer):
    def handle_cond(self, foo, bar):
        # Look up code in database/config file/...
        code = get_code_from_settings()
        eval(code)

(Nota: com um exemplo do mundo real, talvez eu possa dar um exemplo melhor).

Sjoerd Job Postmus
fonte