Убедитесь, что небезопасный код не используется случайно

10

Функция f()использует eval()(или что-то столь же опасное) с данными, которые я создал и сохранил local_fileна компьютере, на котором запущена моя программа:

import local_file

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


a = f(local_file.some_str)

f() безопасен для запуска, так как строки, которые я ему предоставляю, мои.

Однако, если я когда-нибудь решу использовать его для чего-то небезопасного (например, пользовательского ввода), все может пойти ужасно неправильно . Кроме того, если local_fileперестанет быть локальным, это создаст уязвимость, так как мне нужно будет доверять машине, которая также предоставляет этот файл.

Как я должен убедиться, что я никогда не «забуду», что эта функция небезопасна в использовании (если не соблюдены определенные критерии)?

Примечание: eval()опасно и обычно может быть заменено чем-то безопасным.

Парадокс ферми
источник
1
Я обычно предпочитаю отвечать на вопросы, которые задают, вместо того, чтобы говорить «вы не должны этого делать», но я сделаю исключение в этом случае. Я совершенно уверен, что нет никаких причин, почему вы должны использовать eval. Эти функции включены в такие языки, как PHP и Python, как своего рода доказательство того, как много можно сделать с интерпретируемыми языками. Принципиально непостижимо, что вы можете писать код, который создает код, но вы не можете кодировать ту же логику в функцию. Скорее всего, в Python есть обратные вызовы и привязки функций, которые позволят вам делать то, что вам нужно, с таким
user1122069
1
« Мне нужно было бы доверять машине, которая также предоставляет этот файл » - вам всегда нужно доверять машине, на которой вы выполняете свой код.
Берги
Поместите в файл комментарий, в котором говорится: «Эта строка получает eval'd; пожалуйста, не размещайте здесь вредоносный код»?
user253751
@immibis Комментарий будет недостаточным. У меня, конечно, будет огромное «ПРЕДУПРЕЖДЕНИЕ: эта функция использует eval ()» в документации по функции, но я бы предпочел полагаться на что-то гораздо более безопасное, чем это. Например (из-за ограниченного времени) я не всегда перечитываю документы по функциям. Я тоже не думаю, что должен быть. Существуют более быстрые (то есть более эффективные) способы узнать что-либо небезопасно, например, методы, описанные Мерфи в его ответе.
парадокс Ферми
@Fermiparadox, когда вы убираете eval, это становится бесполезным примером. Вы сейчас говорите о функции, которая небезопасна из-за преднамеренного контроля, например, не экранирования параметров SQL. Вы имеете в виду тестовый код, небезопасный для производственной среды? Во всяком случае, теперь я читаю ваш комментарий к ответу Амона - в основном вы искали, как обезопасить свою функцию.
user1122069

Ответы:

26

Определите тип для украшения «безопасных» входов. В вашей функции f()утверждайте, что аргумент имеет этот тип, или выведите ошибку. Будьте достаточно умны, чтобы никогда не определять ярлык def g(x): return f(SafeInput(x)).

Пример:

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

Несмотря на то, что это легко обойти, это усложняет случайность. Люди могут критиковать такую ​​драконовскую проверку типа, но они упустят момент. Поскольку SafeInputтип - это просто тип данных, а не объектно-ориентированный класс, который можно разделить на подклассы, проверка на идентичность типа с type(x) is SafeInputболее безопасна, чем проверка на совместимость с типом isinstance(x, SafeInput). Так как мы хотим применить определенный тип, игнорирование явного типа и просто неявная типизация утки здесь также не являются удовлетворительными. В Python есть система типов, поэтому давайте использовать ее для выявления возможных ошибок!

Амон
источник
5
Небольшое изменение может быть необходимо, хотя. assertудаляется автоматически, так как я буду использовать оптимизированный код Python. Нечто подобное if ... : raise NotSafeInputErrorбудет необходимо.
Парадокс Ферми
4
Если вы все равно пойдете по этому пути, я настоятельно рекомендую включить eval()метод в SafeInput, чтобы вам не требовалась явная проверка типов. Дайте методу имя, которое не используется ни в одном из ваших других классов. Таким образом, вы все равно можете высмеять все это для целей тестирования.
Кевин
@Kevin: Если у вас evalесть доступ к локальным переменным, возможно, код зависит от того, как он изменяет переменные. Перемещая eval в другой метод, он больше не сможет изменять локальные переменные.
Sjoerd Job Postmus
Еще одним шагом может стать то, что SafeInputконструктор получит имя / дескриптор локального файла и выполнит само чтение, гарантируя, что данные действительно поступают из локального файла.
Оуэн
1
У меня нет большого опыта работы с Python, но не лучше ли бросить исключение? В большинстве языков утверждения могут быть отключены и (как я понимаю их) больше для отладки, чем для безопасности. Исключения и выход в консоль гарантируют, что следующий код не будет запущен.
user1122069
11

Как однажды заметил Джоэл, заставьте неправильный код выглядеть неправильно (прокрутите вниз до раздела «Реальное решение», или, что еще лучше, прочитайте его полностью; оно того стоит, даже если оно обращается к переменной вместо имен функций). Поэтому я скромно предлагаю переименовать вашу функцию в нечто вроде f_unsafe_for_external_use()- вы, скорее всего, сами придумаете какую-нибудь схему аббревиатур.

Редактировать: я думаю, что предложение Амона является очень хорошим, основанным на ОО вариантом на ту же тему :-)

Мерфи
источник
0

В дополнение к предложению @amon, вы также можете подумать о комбинировании здесь шаблона стратегии и шаблона объекта метода.

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

    def run(self):
        # do some magic
        pass

И тогда «нормальная» реализация

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

И реализация 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)

(Примечание: на примере реального мира я мог бы привести лучший пример).

Sjoerd Job Postmus
источник