Проверка уязвимостей в коде Python с помощью Bandit

Проверка уязвимостей в коде Python с помощью Bandit

В этом руководстве мы рассмотрим, как простые строки кода могут оказаться разрушительными, и как с помощью Bandit можно их выявить.

Введение

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

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

Уязвимости в Python

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

Вы, вероятно, сталкивались с несколькими из них в своей повседневной деятельности по написанию кода. С некоторыми из наиболее распространенных атак и эксплойтов в значительной степени справляются современные фреймворки и системы, которые предвосхищают такие атаки.

Вот некоторые из них:

Уязвимость OS Command Injection

Основана на модуле subprocess, который используется для выполнения утилит командной строки и вызова процессов, связанных с операционной системой. Следующий фрагмент использует модуль подпроцесса для выполнения поиска DNS и возвращает результат:

import subprocess

input_domain = input("Введите домен: ")
result = subprocess.check_output(
    f"nslookup {input_domain}", shell=True, encoding="UTF-8"
)
print(result)

Что здесь может пойти не так?

В идеальном сценарии конечный пользователь предоставляет имя домена, и сценарий возвращает результаты команды nslookup. Но если вместе с доменом они предоставят команду на базе ОС, например ls, то будет получен следующий результат — команда тоже будет выполнена:

python3 command_inject.py

Введите домен: egorovegor.ru ; ls
Server:         192.168.13.1
Address:        192.168.13.1#53

Non-authoritative answer:
Name:   egorovegor.ru
Address: 92.53.116.135

command_inject.py

Позволив кому-то передать часть команды, мы дали ему доступ к терминалу на уровне ОС.

Представьте себе, насколько разрушительными могут быть последствия, если злоумышленник передаст такую команду, как cat /etc/passwd, которая раскроет пароли существующих пользователей. Как бы просто это ни звучало, модуль subproccess может быть очень рискованным в использовании.

Уязвимость SQL Injection

Атаки SQL Injection в наши дни редки благодаря широко используемым функциям ORM. Но если вы все еще придерживаетесь использования необработанного SQL, вам необходимо знать, как строятся ваши SQL-запросы и насколько безопасно проверяются и передаются параметры запроса.

Рассмотрим следующий фрагмент кода:

from django.db import connection


def find_user(login):
    '''Поиск пользователя в базе данных'''
    with connection.cursor() as cursor_db:
        cursor_db.execute(f"""select login from USERS where name = '{login}'""")
        result = cursor_db.fetchone()
    return result

Вызов функции прост — вы передаете в качестве аргумента строку, скажем, «Ivan», и эта строка вставляется в SQL-запрос, в результате чего получается:

select login from USERS where name = 'Ivan'

Однако, как и в предыдущем случае, если кто-то добавит символ ;, то сможет создать цепочку из нескольких команд. Например, вставка ‘; DROP TABLE USERS; — приведет к следующему:

select login from USERS where name = ''; DROP TABLE USERS; --'

Первый оператор будет запущен прямо перед тем, как база данных сбросит всю таблицу USERS. Вот это да!

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

Библиотека Bandit

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

Установка

Bandit можно установить локально или в виртуальной среде с помощью pip:

pip install bandit

Использование

Bandit можно использовать в следующих кейсах:

  • DevSecOps: включение Bandit как части практики непрерывной интеграции (CI).
  • Разработка: Bandit можно использовать локально как часть локальной настройки разработки, где разработчики могут контролировать эксплуатацию функций до фиксации кода.

Bandit может быть легко интегрирован в CI-тесты, и перед отправкой кода в производство можно проводить общие проверки на уязвимости.

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

Bandit предоставляет пользователям контроль над тем, какие модули использовать, а какие занести в черный список. Этот контроль определяется в конфигурационном файле, который может быть создан с помощью инструмента bandit-config-generator. Результаты выполняемых тестов кода могут быть экспортированы в виде CSV, JSON и т.д.

Конфигурационный файл может быть сгенерирован вот так:

bandit-config-generator -o config.yml

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

bandit -r code/ -f csv -o out.csv

[main]  INFO    profile include tests: None
[main]  INFO    profile exclude tests: None
[main]  INFO    cli include tests: None
[main]  INFO    cli exclude tests: None
[main]  INFO    running on Python 3.8.5
434 [0.. 50.. 100.. 150.. 200.. 250.. 300.. 350.. 400.. ]
[csv]   INFO    CSV output written to file: out.csv

В этом вызове Bandit вы будете указывать каталог проекта с помощью флага -r и записывать вывод в виде CSV с помощью флага -o. Bandit тестирует все скрипты Python внутри этого каталога проекта и возвращает вывод в виде CSV. Вывод очень подробный, и вот как он выглядит:

filenametest_nametest_idissue_severityissue_confidenceissue_textline_numberline_rangemore_info
command_inject.pyblacklistB404LOWHIGHConsider possible security implications associated with subprocess module.1[1, 2]https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess
command_inject.pysubprocess_popen_with_shell_equals_trueB602HIGHHIGHsubprocess call with shell=True identified, security issue.5[4, 5]https://bandit.readthedocs.io/en/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html
sql_inject.pyhardcoded_sql_expressionsB608MEDIUMMEDIUMPossible SQL injection vector through string-based query construction.7[7]https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html

Как уже упоминалось в предыдущем разделе, импорт модуля subprocess и аргумент shell=True представляют высокую угрозу безопасности. Если использование этого модуля и аргумента неизбежно, их можно внести в белый список конфигурационного файла и заставить его пропустить тесты, включив коды B602 (subprocess_popen_with_shell_equals_true) и B404 (import_subprocess) в «skips». Вы можете найти эти коды в сгенерированном конфигурационном файле. Тесты, включенные в файл в секции skips, следующие:

skips: [B602, B404]

Если вы повторно запустите тесты Bandit, используя сгенерированный файл конфигурации, это приведет к созданию пустого CSV-файла, обозначающего, что все тесты были пройдены:

bandit -c config.yml -r code/ -f csv -o out2.csv
[main]  INFO    profile include tests: None
[main]  INFO    profile exclude tests: B404,B602
[main]  INFO    cli include tests: None
[main]  INFO    cli exclude tests: None
[main]  INFO    using config: code/config.yml
[main]  INFO    running on Python 3.8.5
434 [0.. 50.. 100.. 150.. 200.. 250.. 300.. 350.. 400.. ]
[csv]   INFO    CSV output written to file: out2.csv

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

Заключение

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

Егор Егоров

Программирую на Python с 2017 года. Люблю создавать контент, который помогает людям понять сложные вещи. Не представляю жизнь без непрерывного цикла обучения, спорта и чувства юмора.

Ссылка на мой github есть в шапке. Залетай.

Оцените автора
Егоров Егор
Добавить комментарий

  1. Анна

    Очень хорошая статья! Спасибо вам за рекомендации по использованию программы Bandit!

    Ответить
    1. Егор Егоров автор

      пожалуйста, Анна)

      Ответить
  2. ирина

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

    Ответить
    1. Егор Егоров автор

      Пожалуйста, Ирина)

      Ответить