Upewnij się, że niebezpieczny kod nie zostanie przypadkowo użyty

10

Funkcja f()wykorzystuje eval()(lub coś równie niebezpiecznego) z danymi, które utworzyłem i zapisałem local_filena komputerze z uruchomionym programem:

import local_file

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


a = f(local_file.some_str)

f() jest bezpieczny do uruchomienia, ponieważ ciągi, które mu dostarczam, są moje.

Jeśli jednak kiedykolwiek zdecyduję się użyć go do czegoś niebezpiecznego (np. Dane wprowadzone przez użytkownika), sprawy mogą się potoczyć bardzo źle . Ponadto, jeśli local_fileprzestanie być lokalny, stworzy to lukę, ponieważ musiałbym zaufać maszynie, która również udostępnia ten plik.

Jak mam się upewnić, że nigdy nie „zapomnę”, że korzystanie z tej funkcji jest niebezpieczne (chyba że zostaną spełnione określone kryteria)?

Uwaga: eval()jest niebezpieczny i zwykle można go zastąpić czymś bezpiecznym.

Paradoks Fermiego
źródło
1
Zazwyczaj lubię odpowiadać na zadawane pytania, zamiast mówić „nie powinieneś tego robić”, ale zrobię wyjątek w tym przypadku. Jestem całkiem pewien, że nie ma powodu, dla którego powinieneś używać eval. Funkcje te są dołączone do języków takich jak PHP i Python jako swego rodzaju dowód słuszności koncepcji, ile można zrobić z tłumaczonymi językami. Zasadniczo nie można sobie wyobrazić, że można napisać kod, który tworzy kod, ale że nie można zakodować tej samej logiki w funkcji. Python prawdopodobnie ma wywołania zwrotne i powiązania funkcji, które pozwolą ci robić to, czego potrzebujesz
user1122069
1
Musiałbym zaufać maszynie, która również udostępnia ten plik ” - zawsze musisz ufać maszynie, na której wykonujesz swój kod.
Bergi,
Umieścić w pliku komentarz „Ten ciąg zostaje ewaluowany; proszę nie umieszczać tutaj szkodliwego kodu”?
user253751
@immibis Komentarz nie byłby wystarczający. Oczywiście będę mieć ogromne „OSTRZEŻENIE: Ta funkcja używa eval ()” w dokumentacji funkcji, ale wolałbym polegać na czymś znacznie bezpieczniejszym. Na przykład (z powodu ograniczonego czasu) nie zawsze ponownie czytam dokumenty funkcji. Nie uważam też, że powinienem być. Istnieją szybsze (czyli bardziej wydajne) sposoby, aby wiedzieć, że coś jest niebezpieczne, na przykład metody, na które Murphy powiązał odpowiedź.
Paradoks Fermi
@Fermiparadox, kiedy wyjmiesz eval z pytania, staje się bez przydatnego przykładu. Czy mówisz teraz o funkcji, która nie jest bezpieczna z powodu celowego niedopatrzenia, takiego jak brak zmiany parametrów SQL. Czy masz na myśli kod testowy, który nie jest bezpieczny dla środowiska produkcyjnego? W każdym razie, teraz czytam twój komentarz do odpowiedzi Amona - w zasadzie szukałeś sposobu na zabezpieczenie swojej funkcji.
user1122069,

Odpowiedzi:

26

Zdefiniuj typ ozdabiania „bezpiecznych” wejść. W swojej funkcji sprawdź f(), czy argument ma ten typ, lub wyrzuć błąd. Bądź wystarczająco inteligentny, aby nigdy nie definiować skrótu def g(x): return f(SafeInput(x)).

Przykład:

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))

Chociaż można to łatwo obejść, utrudnia to przypadkowo. Ludzie mogą krytykować taką drakońską kontrolę typu, ale nie rozumieją o co chodzi. Ponieważ SafeInputtyp jest tylko typem danych, a nie klasą obiektową, którą można podklasować, sprawdzanie tożsamości typu za pomocą type(x) is SafeInputjest bezpieczniejsze niż sprawdzanie zgodności z typem isinstance(x, SafeInput). Ponieważ chcemy wymusić określony typ, ignorowanie typu jawnego i samo pisanie niejawnego kaczego tekstu również tutaj nie jest satysfakcjonujące. Python ma system typów, więc użyjmy go, aby wyłapać ewentualne błędy!

amon
źródło
5
Może być jednak potrzebna niewielka zmiana. assertjest usuwany automatycznie, ponieważ będę korzystał ze zoptymalizowanego kodu python. if ... : raise NotSafeInputErrorPotrzebne byłoby coś takiego .
Paradoks Fermi,
4
Jeśli mimo to idziesz tą drogą, zdecydowanie zalecam przejście eval()na metodę SafeInput, aby nie było potrzeby jawnego sprawdzania typu. Nadaj metodzie nazwę, która nie jest używana w żadnej z innych klas. W ten sposób nadal możesz wyśmiewać to wszystko do celów testowych.
Kevin
@Kevin: Widząc as evalma dostęp do zmiennych lokalnych, być może kod zależy od tego, jak mutuje zmienne. Przenosząc eval do innej metody, nie będzie on już mógł mutować zmiennych lokalnych.
Sjoerd Job Postmus
Kolejnym krokiem może być uzyskanie przez SafeInputkonstruktora nazwy / uchwytu pliku lokalnego i wykonanie samego odczytu, upewniając się, że dane rzeczywiście pochodzą z pliku lokalnego.
Owen
1
Nie mam dużego doświadczenia z Pythonem, ale czy nie lepiej rzucać wyjątek? W większości języków twierdzenia można wyłączyć i są (tak jak je rozumiem) bardziej do debugowania niż do bezpieczeństwa. Wyjątki i wyjście z konsoli gwarantują, że następny kod nie zostanie uruchomiony.
user1122069,
11

Jak kiedyś zauważył Joel, spraw, aby zły kod wyglądał źle (przewiń w dół do sekcji „Prawdziwe rozwiązanie” lub lepiej przeczytaj go całkowicie; warto, nawet jeśli adresuje zmienną zamiast nazw funkcji). Dlatego pokornie sugeruję zmienić nazwę funkcji na coś w stylu f_unsafe_for_external_use()- najprawdopodobniej sam wymyślisz jakiś schemat skrótów.

Edycja: Myślę, że sugestia Amona jest bardzo dobrą, opartą na OO odmianą tego samego tematu :-)

Murphy
źródło
0

Uzupełniając sugestię @amon, możesz również pomyśleć o połączeniu wzorca strategii tutaj, a także wzorca obiektu metody.

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

    def run(self):
        # do some magic
        pass

A potem „normalna” implementacja

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

I implementacja 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)

(Uwaga: na przykładzie z prawdziwego świata mógłbym dać lepszy przykład).

Sjoerd Job Postmus
źródło