Переконайтеся, що небезпечний код не використовується випадково


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; будь ласка, не ставте тут шкідливий код"?
користувач253751

@immibis Коментар буде недостатнім. Я, звичайно, матиму величезне "ПОПЕРЕДЖЕННЯ: Ця функція використовує eval ()" у документах функції, але я б скоріше покладався на щось набагато безпечніше, ніж це. Наприклад, (через обмежений час) я не завжди перечитую документи документа. Ні я не думаю, що я повинен бути таким. Існують більш швидкі (тобто ефективніші) способи пізнати щось небезпечно, як методи, пов'язані Мерфі у своїй відповіді.
Парадокс Фермі

@Fermiparadox, коли ви виймаєте eval з питання, це стає без корисного прикладу. Ви зараз говорите про функцію, яка є небезпечною через навмисний контроль, наприклад, не уникнення параметрів SQL. Ви маєте на увазі тестовий код, не безпечний для виробничого середовища? У всякому разі, зараз я прочитав ваш коментар до відповіді Амона - в основному ви шукали, як забезпечити свою функцію.
користувач1122069

Відповіді:


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 в інший метод, він більше не матиме можливості мутувати локальні змінні.
Joe Postbut

Наступним кроком може бути, щоб SafeInputконструктор взяв ім’я / обробку локального файлу та зробив сам читання, гарантуючи, що дані дійсно надходять із локального файлу.
Оуен

1
Я не маю багато досвіду роботи з python, але хіба не краще кинути виняток? У більшості мов твердження можуть бути відключені і є (наскільки я їх розумію) більше для налагодження, ніж для безпеки. Винятки та вихід із консолі гарантують, що наступний код не буде запущений.
користувач1122069

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

І реалізація адміністратором-введенням-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.