Рубрики
Без рубрики

Общие ловушки безопасности на питоне и как их избежать

Популярность Python привлекает черно-белых хакеров. Вот как решения SCA, такие как Bandit, могут помочь нам найти и исправить недостатки безопасности в наших приложениях Python. Tagged с Python, Pythonsecurity.

Python, несомненно, популярный язык. Он неизменно входит в число самых популярных и самых любимых языков год после год Анкет Это не сложно объяснить, учитывая, насколько он свободно и выразительен. Его псевдокодный синтаксис позволяет начинающим воспринимать его в качестве своего первого языка, в то время как его обширная библиотека пакетов (включая таких, как гиганты, такие как Джанго и Тензорфлоу) гарантируется, что он масштабируется для любой необходимой для этого задачи.

Быть таким широко используемым языком делает Python очень привлекательной целью для вредоносных хакеров. Давайте посмотрим на несколько простых способов защитить ваши приложения Python и держать черные хаты в страхе.

Проблемы и решения

Python придает большое значение дзену или счастью для разработчика. Самое ясное доказательство этого заключается в том, что руководящие принципы Python приведены в стихотворении. Попробуйте импортировать это в оболочке Python, чтобы прочитать его. Вот некоторые проблемы безопасности, которые могут нарушить ваш дзен, а также решения для восстановления его в состоянии спокойствия.

Небезопасная десериализация

OWASP TOP TEVE Основной контрольный список для веб -безопасности упоминает небезопасную деестерилизацию как один из десяти наиболее распространенных недостатков безопасности. Хотя общеизвестно, что выполнение чего -либо, исходящего от пользователя, является ужасной идеей, сериализация и десеризация пользовательского ввода не кажутся одинаково серьезными. В конце концов, код не работает, верно? Неправильный,

Pyyaml – это De-Facto Стандарт для сериализации и десериализации YAML в Python. Библиотека поддерживает сериализацию пользовательских типов данных для YAML и десериализации их обратно в объекты Python. Смотрите этот код сериализации здесь и YAML, созданный им.

# serialize.py
import yaml class Person: def __init__ (self, name, age): self.name = name self.age = age def __str__ (self): return f'' def __repr__ (self): return str(self) person = Person('Dhruv', 24)
with open('person.yml', 'w') as output_file: yaml.dump(person, output_file)
!!python/object: __main__.Person
age: 24
name: Dhruv

Deserialization Этот YAML возвращает исходный тип данных.

# deserialize.py
import yaml # class Person needs to be present in the scope
with open('person.yml', 'r') as input_file: person = yaml.load(input_file, Loader = yaml.Loader) print(person)
$ python deserialize.py ↵

Как видите, строка !! Python/Object: __main __. Person В YAML описывается, как повторно устроить объекты из своих текстовых представлений. Но это открывает Удар векторов атаки, которые могут обостриться в RCE Когда этот экземпляр может выполнить код.

Решение

Решение, настолько тривиальное, что это ни казалось, состоит в том, чтобы использовать безопасную загрузку, заменив погрузчик Ямл. Загрузчик в пользу Ямл. SafeLoader погрузчик. Этот погрузчик безопаснее, потому что он полностью блокирует загрузку пользовательских классов.

# deserialize.py
import yaml # class Person needs to be present in the scope
with open('person.yml', 'r') as input_file: person = yaml.load(input_file, Loader = yaml.SafeLoader) print(person)
$ python deserialize.py ↵
ConstructorError: could not determine a constructor for the tag 'tag:yaml.org,2002:python/object: __main__.Person'
  in "person.yml", line 1, column 1

Стандартные типы, такие как хэши и массивы, все еще могут быть сериализованы и десериализованы из документов YAML, как и раньше. Большинство людей, вероятно, включая вас, даже не поймут разницу.

age: 24
name: Dhruv
$ python deserialize.py ↵
{'age': 24, 'name': 'Dhruv'}

Динамическое исполнение

Python имеет пару очень опасных функций: exec и оценка . Оба очень похожи с точки зрения того, что они делают: обрабатывать строки переданы им в виде кода Python. exec Ожидает, что строка будет оператором, который она выполнит и не вернет значение. eval Ожидает, что строка будет выражением и вернет вычисленное значение выражения.

Вот пример того, что обе эти функции в действии.

eval('2 + 5') # returns 7
exec('print("Hello")') # prints "Hello", no return

Теоретически вы могли бы передать заявление eval и получить такой же эффект, как exec , потому что в возвращении питона Нет практически такой же, как вообще ничего не возвращать.

eval('print("Hello")') # prints "Hello", returns None

Опасность этих функций заключается в способности этих функций выполнять практически любой код в том же процессе Python. Передача любого ввода в функцию, о которой вы не можете быть на 100% уверены, сродни передаче ключей сервера злым хакерам на тарелке. Это само определение RCE.

Решение

Есть способы смягчить доступ, который имеет Eval. Вы можете ограничить доступ к глобальным и местным жителям, передавая словари в качестве второго и третьего аргумента в eval соответственно. Помните, что местные жители принимают приоритет над глобалами в случае конфликта.

x = 3
eval('x + 5') # returns 8
eval('x + 5', { 'x': 2 }) # returns 7
eval('x + 5', { 'x': 2 }, { 'x': 1 }) # returns 6

Это делает код более безопасным, да. По крайней мере, это несколько предотвращает утечку данных в переменных.

Но это все еще не мешает строке получить доступ к каким-либо встроенным средствам, таким как Пау , или более опасно, __import__ Анкет Чтобы противостоять этому, вам нужно переопределить __builtins__ Анкет

eval(" __import__ ('math').sqrt(5)", {}, {}) # returns 2.2360679774997898
eval( " __import__ ('math').sqrt(5)", { " __builtins__": None }, {} # restricts access to built-ins
) # error

Безопасно разоблачить сейчас? Не совсем. Потому что независимо от того, насколько защищены вы оценка Это задача состоит в том, чтобы оценить выражение, и ничто не может остановить выражение слишком долго и заморозить сервер.

eval("2 ** 2147483647", { " __builtins__": None }, {}) # goodbye!

Как нам нравится говорить, разработчики Python « Eval это зло ». »

Управление зависимостью

Популярность Python привлекает внимание исследователей безопасности белого хэта так же, как и хакеров со злыми намерениями. В результате новые уязвимости безопасности, которые постоянно обнаруживаются, раскрываются и исправляются. Чтобы держать вредоносных хакеров в страхе, ваше программное обеспечение должно поддерживать все свои зависимости.

Общей методикой для прикрепления пакетов в Python является повсеместное Требования.txt Файл, простой файл, в котором перечислены все зависимости и точные версии, необходимые для вашего проекта.

Допустим, вы установите Django. На момент написания, Django зависит от еще трех пакетов. Если вы замораживаете свои зависимости, вы получите следующие требования. Обратите внимание, что только одна из этих зависимостей была установлена вами, остальные 3 являются подзадажными.

$ pip freeze ↵
asgiref==3.3.1
Django==3.1.5
pytz==2020.5
sqlparse==0.4.1

PIP FREEZE не помещает зависимости на уровнях, и это проблема. Для небольших проектов с несколькими зависимостями, которые вы можете отслеживать умственно, это не имеет большого значения, но по мере роста ваших проектов зависит и зависимости от ваших высшего уровня. Конфликты возникают, когда подзадачности пересекаются. Обновление отдельных зависимостей также является беспорядком, потому что графическая связь этих зависимостей не ясна из простого текстового файла.

Решение

Pipenv и Поэзия Два инструмента, которые помогают вам лучше управлять зависимостями. Я предпочитаю Pipenv, но поэзия одинаково хороша. Оба менеджера пакетов строятся на вершине пип . Забавный факт: DeepSource совместим как с Pipenv, так и с поэзией в качестве менеджеров пакетов.

Pipenv, например, отслеживает ваши зависимости верхнего уровня в Pipfile а затем выполняет тяжелую работу по блокированию зависимостей в имени блокировки Pipfile.lock , аналогично тому, как npm Управляет Node.js Packages. Вот пример Pipfile Анкет

[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
django = "*"

[requires]
python_version = "3.9"

При этом вы можете получить четкое представление о зависимостях вашего приложения на верхнем уровне. Обновление зависимостей также намного проще, потому что вам просто нужно обновить пакеты верхнего уровня, а алгоритм блокировки выяснят наиболее совместимые и современные версии всех подзадач.

Вот тот же пример с Джанго. Обратите внимание, как Pipenv может определить ваши зависимости высшего уровня и ее зависимости и так далее.

$ pipenv graph ↵
Django==3.1.5
  - asgiref [required: >=3.2.10,<4, installed: 3.3.1]
  - pytz [required: Any, installed: 2020.5]
  - sqlparse [required: >=0.2.2, installed: 0.4.1]

Если ваш код размещен на GitHub, убедитесь, что вы включаете и настроили DEVIVEBOT также. Это изящный маленький бот, который предупреждает вас, если какая -либо из ваших зависимостей не устарела или если уязвимость была определена в закрепленной версии зависимости. Devidebot также сделает PRS в вашем репо, автоматически обновляя ваши пакеты. Действительно очень удобно!

Утверждения времени выполнения

Python имеет специальное утверждение Ключевое слово для защиты от неожиданных ситуаций. Цель утверждение это просто, проверьте условие и поднимите ошибку, если условие не выполнено. По сути, утверждение оценивает данное выражение и

  • Если он оценивается в правдивую ценность, движется вперед
  • Если он оценивается до ложного значения, поднимает AssertionError с данным сообщением

Рассмотрим этот пример.

def do_something_dangerous(user, command): assert user.has_permissions(command), f'{user} is not authorized' user.execute(command)

Это очень простой пример, в котором мы проверяем, есть ли у пользователя разрешения на выполнение действия и впоследствии выполнить заданное действие. В этом случае, если user.has_permissions () Возвращает Ложный , наше утверждение вызвало бы AssertionError и исполнение будет остановлено. Кажется довольно безопасным, верно?

Нет. Утверждения являются инструментами для разработчиков на этапах разработки и отладки. Заявления не должны использоваться для защиты критических функций. Постоянная питона, __debug__ установлен на Ложный Во время компиляции, которая удаляет операторы ASSERT из скомпилированного кода для оптимизации кода для производительности. Удаление операторов ASSERT из скомпилированного кода оставляет функцию безрассудной.

Еще одна причина избегать утверждение S, что ошибки утверждения не помогают отладчикам, поскольку они не предоставляют никакой информации, кроме того, что утверждение не выдерживает. Определение классов исключений APT, а затем повышение их экземпляров – это гораздо более надежное решение.

Решение

Для альтернативного подхода вернитесь к основам. Вот та же программа, на этот раз, используя Если/else и поднятие RessisseError (Вам нужно где -то определить его), когда необходимое утверждение будет невыполнено.

def do_something_dangerous(user, command): if user.has_permissions(command): # [safer as it will not be removed by compiler user.execute(command) else: raise PermissionError(f'{user} is not authorized') # suitable error class

Этот код использует простые конструкции Python, работает так же с __debug__ установить в Верно или Ложный и поднимает четкие исключения, которые можно обработать с гораздо большей ясностью.

Достижение дзен

Ключевой вывод из всех этих примеров – никогда не доверять вашим пользователям. Ввод, предоставленный пользователями, не должен быть сериализованным, оцениваемым, оцененным, выполненным или визуализированным. Чтобы быть в безопасности, вы должны быть осторожны с тем, что вы пишете, и тщательно проверять код после его написания.

Знаете ли вы, что лучше, чем сканирование уязвимостей в коде после его написания? Получение уязвимостей выделяется, как только вы пишете с ними код.

Бандит

Инструменты анализа статического кода Такие, как Линтеры и Сканеры уязвимости могут помочь вам найти много проблем, прежде чем они будут эксплуатируются в дикой природе. Отличный инструмент для поиска уязвимостей безопасности в Python – Бандит . Бандит проходит каждый файл, генерирует абстрактное синтаксисное дерево (AST) для него, а затем запускает целый ряд тестов на этом AST. Bandit может обнаружить целую кучу уязвимостей вне коробки, а также может быть расширен для конкретных сценариев и совместимости с фреймворками через плагины. На самом деле, Бандит способен обнаружить все вышеупомянутые недостатки безопасности.

Если вы разработчик Python, я не могу рекомендовать Bandit. Я мог бы написать целую статью, превозносив свою достоинства. Я мог бы даже сделать это.

Глубокий

Вам также следует рассмотреть возможность автоматизации всего этого процесса аудита и просмотра с использованием инструментов автоматизации кода, таких как DeepSource, который сканирует ваш код, на каждом коммите и для каждого PR, через его ландтер и анализаторы безопасности и может автоматически решать множество проблем. Deepsource Также имеет свои собственные пользовательские анализаторы для большинства языков, которые постоянно улучшаются и поддерживаются. И это Невероятно легко настроить !

version = 1

[[analyzers]]
name = "python"
enabled = true

  [analyzers.meta]
  runtime_version = "3.x.x"
  max_line_length = 80

Кто знал, что это может быть так просто?

Познакомьтесь с дзеном питона и будьте осторожны, чтобы не позволить черным хатам нарушать ваш мир!

Оригинал: “https://dev.to/deepsource/common-python-security-pitfalls-and-how-to-avoid-them-50gh”