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


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)

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.