Как выявить слабости исходного кода python
Перейти к содержимому

Как выявить слабости исходного кода python

  • автор:

Как найти уязвимость в Python приложении?

Как найти уязвимость в Python приложении?

Существует много комплексных онлайн сканеров для тестирования на безопасность, но они не могут быть в состоянии обнаружить конкретную слабость в платформе, как Python, Node.js . и т.п.

Давайте посмотрим на список сканеров, чтобы найти угрозу безопасности в приложении Python.

1. PYT (Python Taint)

Инструмент статического анализа с открытым исходным кодом для защиты от внедрения команд, межсайтового скриптинга (XSS), SQL инъекций и атак через каталог в веб-приложениях на Python.

Если вы хотели бы внести свой вклад в PYT, то вы можете присоединиться к их slack группе.

2. Bandit

Bandit — это бесплатный инструмент с открытым исходным кодом, для поиска уязвимости в вашем Python коде. Данный инструмент обрабатывает каждый файл и генерирует отчет.

Вы можете установить его с помощью pip .

Вы можете настроить конфигурацию проверки Bandit. По умолчанию тест проводится для всех профилей, однако, если вы хотите проверить только ShellInjection, используйте команду ниже.

bandit samples/*.py -p ShellInjection

Вы также можете составить отчет в зависимости от уровня серьезности (низкий, средний или высокий).

3. Pyntch

Инструмент Pyntch поддерживает только Python 2.x, статический анализатор кода для обнаружения возможных ошибок во время выполнения.

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

Это быстрый инструмент способный сканировать тысячи строк в минуту.

4. Spaghetti

Удобный сканер с открытым исходным кодом на основе Python для обнаружения неверной конфигурации, небезопасных файлов и поддержки веб-фреймворков, таких как CherryPy, CakePHP и т.д.

python spaghetti

Spaghetti способен обнаруживать различные атаки, включая следующие:

  • Атака «грубой силой» (Brutforce)
  • Взлом кредитной карты, электронной почты, раскрытие IP
  • HTML / SQL / LDAP / XPATH / XSS
  • ShellShock, Crime, Struts-shock
  • Анонимный шифр (Anonymous cipher)

5. RATS (Rough Auditing Tools for Security)

Инструмент RATS выполняет анализ кода Python, PHP, Perl, C ++ и выделяет ошибки, связанные с безопасностью, как показано ниже:

  • Время проверки
  • Время использования
  • Переполнение буфера

6. Acunetix

Acunetix проверяет ваш сайт на наличие более 5000 разновидных уязвимостей и предоставляет подробный отчет с рекомендациями по исправлению. Это комплексная платформа сканирования уязвимостей для тестирования веб-приложений.

Pychecker

Одним из наиболее интересных инструментов для анализа исходного кода Python программы является Pychecker. Как и lint для языка C, Pychecker позволяет выявлять слабости в исходном коде на языке Python. Можно рассмотреть следующий пример с использованием Pychecker:

import re, string

from string import split

print split(a) + x

Pychecker выдаст следующие предупреждения:

badcode.py:1: Imported module (string) not used

badcode.py:2: Imported module (re) not used

badcode.py:2: Module (re) re–imported

badcode.py:5: Parameter (y) not used

badcode.py:6: Using import and from . import for (string)

badcode.py:7: Local variable (a) shadows global defined on line 3

badcode.py:8: Local variable (a) shadows global defined on line 3

В первой строке импортирован модуль, который далее не применяется, то же самое с модулем re. Кроме того, модуль re импортирован повторно. Другие проблемы с кодом: параметр y не использован; модуль string применен как в операторе import, так и во from–import; локальная переменная a затеняет глобальную, которая определена в третьей строке.

Можно переписать этот пример так, чтобы Pychecker выдавал меньше предупреждений:

print string.split(a1) + x

Теперь имеется лишь одно предупреждение:

goodcode.py:4: Parameter (y) not used

Такое тоже бывает. Программист должен лишь убедиться, что он не сделал ошибки.

Статический анализ больших объёмов Python-кода: опыт Instagram. Часть 1

Серверный код в Instagram пишут исключительно на Python. Ну, в основном это именно так. Мы используем немного Cython, а в состав зависимостей входит немало C++-кода, с которым можно работать из Python как с C-расширениями.

Наше серверное приложение — это монолит, представляющий собой одну большую кодовую базу, состоящую из нескольких миллионов строк и включающую в себя несколько тысяч конечных точек Django (вот выступление, посвящённое использованию Django в Instagram). Всё это загружается и обслуживается как единая сущность. Из монолита выделено несколько сервисов, но в наши планы не входит сильное разделение монолита.

Наша серверная система — это монолит, который очень часто меняется. Каждый день сотни программистов делают сотни коммитов в код. Мы непрерывно разворачиваем эти изменения, делая это каждые семь минут. В результате развёртывание проекта в продакшне выполняется около ста раз за сутки. Мы стремимся к тому, чтобы между попаданием коммита в ветку master и развёртыванием соответствующего кода в продакшне проходило бы менее часа (вот выступление об этом, сделанное на PyCon 2019).

Очень сложно поддерживать эту огромную монолитную кодовую базу, делая в неё ежедневно сотни коммитов, и при этом не довести её до состояния полного хаоса. Мы хотим сделать Instagram местом, работая в котором, программисты могут быть продуктивными и способными быстро готовить к выходу новые полезные возможности системы.

Этот материал посвящён тому, как мы пользуемся линтингом и автоматическим рефакторингом для того чтобы облегчить управление кодовой базой, написанной на Python.

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

Linting: документация, которая появляется там, где она нужна

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

Разновидности линтинга

Линтинг — это всего лишь одна из многих разновидностей статического анализа кода, которые мы используем в Instagram.

Самый примитивный способ реализации правил линтинга — это использование регулярных выражений. Регулярные выражения писать несложно, но Python — это не «регулярный» язык. В результате очень сложно (а иногда и невозможно) надёжно искать паттерны в Python-коде с помощью регулярных выражений.

Если говорить о самых сложных и продвинутых способах реализации линтеров, то тут находятся инструменты вроде mypy и Pyre. Это — две системы для статической проверки типов Python-кода, которые могут выполнять глубокий анализ программ. В Instagram используется Pyre. Это — мощные инструменты, но их тяжело расширять и настраивать.

Когда мы говорим о линтинге в Instagram, мы обычно имеем в виду работу с простыми правилами, основанными на абстрактном синтаксическом дереве. Именно нечто подобное лежит в основе написания наших собственных правил линтинга для серверного кода.

Когда Python выполняет модуль, он начинает работу с запуска парсера и с передачи ему исходного кода. Благодаря этому создаётся дерево разбора — разновидность конкретного синтаксического дерева (concrete syntax tree, CST). Это дерево представляет собой свободное от потерь представление входного исходного кода. В этом дереве сохранена каждая деталь вроде комментариев, скобок и запятых. На основе CST можно полностью восстановить изначальный код.

Дерево разбора Python (разновидность CST), сгенерированное lib2to3

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

Python компилирует дерево разбора в абстрактное синтаксическое дерево (abstract syntax tree, AST). Некоторые сведения об исходном коде при таком преобразовании теряются. Речь идёт о «дополнительной синтаксической информации» — вроде комментариев, скобок, запятых. Однако семантика кода в AST сохраняется.

Абстрактное синтаксическое дерево Python, сгенерированное модулем ast

Мы разработали LibCST — библиотеку, которая даёт нам лучшее из миров CST и AST. Она даёт представление кода, в котором сохраняется вся информация о нём (как в CST), но из такого представления кода легко извлекать семантические сведения о нём (как при работе с AST).

Представление конкретного синтаксического дерева LibCST

Наши правила линтинга используют синтаксическое дерево LibCST для нахождения паттернов в коде. Это синтаксическое дерево, на высоком уровне, легко исследовать, оно позволяет избавиться от проблем, которые сопутствуют работе с «нерегулярным» языком.

Предположим, что в некоем модуле имеется циклическая зависимость из-за импорта типа. Python позволяет решить эту проблему, поместив команды импорта типов в блок if TYPE_CHECKING . Это — защита от импорта чего-либо во время выполнения программы.

# команды импорта значений from typing import TYPE_CHECKING from util import helper_fn # команды импорта типов if TYPE_CHECKING: from circular_dependency import CircularType

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

# команды импорта значений from typing import TYPE_CHECKING from util import helper_fn # команды импорта типов if TYPE_CHECKING: from circular_dependency import CircularType if TYPE_CHECKING: # Дублирование защитного механизма! from other_package import OtherType

От этой избыточности можно избавиться с помощью правила линтера!

Начнём с инициализации счётчика «защитных» блоков, найденных в коде.

class OnlyOneTypeCheckingIfBlockLintRule(CstLintRule): def __init__(self, context: Context) -> None: super().__init__(context) self.__type_checking_blocks = 0

Затем, встречая соответствующее условие, мы инкрементируем счётчик, и проверяем, чтобы в коде было бы не более одного подобного блока. Если это условие не соблюдается — мы генерируем предупреждение в соответствующем месте кода, вызывая вспомогательный механизм, используемый для формирования подобных предупреждений.

def visit_If(self, node: cst.If) -> None: if node.test.value == "TYPE_CHECKING": self.__type_checking_blocks += 1 if self.__type_checking_blocks > 1: self.context.report( node, "More than one 'if TYPE_CHECKING' section!" )

Подобные правила линтинга работают, просматривая дерево LibCST и собирая информацию. В нашем линтере это реализовано с помощью паттерна «посетитель» (Visitor). Как вы могли заметить, правила переопределяют методы visit и оставляют методы, связанные с типом узла. Эти «посетители» вызываются в определённом порядке.

class MyNewLintRule(CstLintRule): def visit_Assign(self, node): . # вызывается первым def visit_Name(self, node): . # вызывается для каждого потомка def leave_Assign(self, name): . # вызывается после обработки всех потомков

Методы visit вызываются до посещения потомков узлов. Методы leave вызываются после посещения всех потомков

Мы придерживаемся принципов работы, в соответствии с которыми в первую очередь решаются простые задачи. Наше первое собственное правило линтера было реализовано в одном файле, содержало одного «посетителя» и использовало разделяемое состояние.

Один файл, один «посетитель», использование разделяемого состояния

Класс Single Visitor должен иметь сведения о состоянии и о логике всех наших правил линтинга, не связанных с ним. При этом не всегда очевидно то, какое состояние соотносится с конкретным правилом. Такой подход хорошо показывает себя в ситуации, когда имеется буквально несколько собственных правил линтинга, но у нас таких правил около сотни, что крайне осложнило поддержку паттерна single-visitor .

Сложно узнать о том, какое состояние и логика связаны с каждой из проводимых проверок

Конечно, в качестве одного из возможных решений этой проблемы можно было бы рассмотреть определение нескольких «посетителей» и организацию такой схемы работы, чтобы каждый из них каждый раз просматривал бы всё дерево. Однако это привело бы к серьёзному падению производительности, а линтер — это программа, которая должна работать быстро.

Каждое правило линтера может повторно обходить дерево. При обработке файла правила выполняются последовательно. Однако такой подход, при котором часто выполняется обход дерева, привёл бы к серьёзному падению производительности

Вместо того чтобы реализовать у себя нечто подобное, мы вдохновились линтерами, используемыми в экосистемах других языков программирования — вроде ESLint из JavaScript, и создали централизованный реестр «посетителей» (Visitor Registry).

Централизованный реестр «посетителей». Мы можем эффективно определить то, какой узел интересует каждое правило линтера, экономя время на узлах, которые его не интересуют

Когда правило линтера инициализируют, все переопределения методов правила хранятся в реестре. Когда мы обходим дерево, мы смотрим на всех зарегистрированных «посетителей» и вызываем их. Если метод не реализован — это значит, что вызывать его не нужно.

Это снижает потребление системой вычислительных ресурсов при добавлении в неё новых правил линтинга. Обычно мы проверяем линтером небольшое количество недавно изменённых файлов. Но мы можем выполнить проверку всех правил на всей серверной кодовой базе Instagram в параллельном режиме всего за 26 секунд.

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

class MyCustomLintRuleTest(CstLintRuleTest): RULE = MyCustomLintRule VALID = [ Valid("good_function('this should not generate a report')"), Valid("foo.bad_function('nor should this')"), ] INVALID = [ Invalid("bad_function('but this should')", "IG00"), ]

Уважаемые читатели! Пользуетесь ли вы линтерами?

Python проверка кода на безопасность

Python не первый год входит в Топ-4 языков программирования по популярности. Его используют для различных целей:

  • При автоматизации задач в системном администрировании.
  • В банковской сфере. Например, один из крупнейших банков России применяет его для управления своими банкоматами.
  • Во встроенных системах станков с ЧПУ.
  • В телекоммуникационном оборудовании.
  • При разработке мобильных приложений (преимущественно для серверной части).
  • В Data Science, при разработке облачных решений, в области машинного обучения, искусственного интеллекта.

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

Методы и инструменты тестирования уязвимости Python

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

  • DAST – динамическое тестирование методом черного ящика. Выявляет проблемы в работающем приложении – воздействуя на интерфейсы программы и анализируя ее реакцию на такие события. Для анализа уязвимости написанных на Python продуктов с помощью этого метода требуются специалисты в области тестирования на проникновение. Также необходимо развертывание отдельных сред для тестирования, что могут позволить себе далеко не все конечные пользователи программного обеспечения.
  • SAST – тестирование безопасности Python методом белого ящика. Для SAST-анализатора исходного кода не требуется запуск приложения. Кроме того, результаты работы этого инструмента представляются в удобном для восприятия виде: с ними может работать специалист, не имеющий опыта в разработке (офицер по информационной безопасности, системный администратор). Более того, SAST-инструменты могут использовать и конечные пользователи программ. Еще одно преимущество этот метода при тестировании уязвимости – проверка не только самой программы, но и сторонних компонентов и библиотек, что для этого языка программирования с присущим ему огромным количеством зависимостей весьма актуально.
  • IAST – интерактивное тестирование безопасности. В методе сочетаются черты двух предыдущих. за счет чего покрывается большее количество кода, выдаются более точные результаты, а также достигается ряд других преимуществ. Но чтобы использовать его, потребуется собственная команда разработчиков для интерпретации некоторых результатов, а также развертывание отдельных сред для тестирования.

SAST-тестирование безопасности Python

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

Тестирование уязвимости с помощью SAST-инструментов позволяет выявлять как «универсальные», характерные для многих языков программирования уязвимости (например, места в коде, через которые возможны атаки с помощью инъекций или парсинг XML), так и специфичные. SAST-анализатор исходного кода находит конструкции, через которые может быть реализована угроза, и формирует оповещения об этом.

При проверке уязвимостей методом белого ящика выявляются слабые места в работе с вводимыми пользователями и принимаемыми от сторонних источников данными. SAST находит проблемы с авторизацией, работой с базами данных, сессиями и так далее. За счет покрытия почти 100% кода практически ни одна уязвимость не остается незамеченной.

Проверка безопасности Python с помощью SAST-анализаторов позволяет находить практически все типовые проблемы, характерные для этого языка. Среди них:

  • Уязвимости, связанные с использованием в процессе разработки старых версий языка и небезопасных конструкций, из-за чего существует вероятность реализации некоторых угроз. Пример – функция init() в Python 2.7, которая работает аналогично небезопасной eval в более новых версиях языка. Через нее возможно выполнение сторонних скриптов (которые могут быть вредоносными). Пример небезопасной конструкции – использование assert для защиты фрагментов кода, к которым не должны обращаться пользователи.
  • Публичное отображение ошибок. Это может отрицательно повлиять на безопасность приложения. SAST-инструмент может находить такие моменты (пример – значение true для debug при использовании Django), опираясь на стандартные или добавляемые пользователями правила.
  • Пакеты с вредоносным кодом в модулях и уязвимости, проблемы, связанные с импортированием. При разработке программного обеспечения на этом языке программирования может использоваться множество библиотек, устанавливающихся с помощью Pip. Инструменты статического анализа кода проверяют не только само приложение, но и подключаемые компоненты. Еще одна потенциальная угроза для программ на Python – использование относительных и неявных путей для импорта. Такие проблемы выявляются с помощью стандартных правил SAST-инструментов, а также правил, задаваемых пользователями.

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *