Пропустити навігацію EPAM

Безпека в Python: як ми захищаємо код від найпоширеніших вразливостей

Лайфхаки
  • Python

Кібербезпека та захист коду від різних вразливостей стають критично важливими в поточних умовах зростаючих загроз. Експерти Cybersecurity Ventures прогнозують, що глобальна кіберзлочинність завдасть збитків на $8 трлн доларів за підсумками 2023 року, а у 2024-2025 роках ця сума перевищить $10 трлн. Проте навіть одиничний витік даних коштує організації в середньому $4,3 млн.

Ми живемо в час, коли безпека програмного забезпечення визначає життєздатність бізнесу. Python, як одна з найпопулярніших мов програмування, також не позбавлена вразливостей. Наприклад, деякі помилки в коді можуть отримати оцінку CVSS 8.6 (висока критичність) та дозволити віддалену експлуатацію без втручання людини.

Крім того, загрози постійно еволюціонують. За прогнозами експертів, до 2031 року успішна ransomware-атака буде відбуватися вже кожні 2 секунди. Отже, розуміння та впровадження надійних методів захисту Python-коду стає не просто рекомендацією, а необхідністю.

У цій статті ми розглянемо найпоширеніші вразливості в Python-коді та поділимося нашим досвідом їх усунення. Також ми детально опишемо інструменти статичного та динамічного аналізу, їхню інтеграцію в CI/CD та ефективні комбінації для забезпечення повного захисту вашого коду.

ПРИЄДНУЙСЯ ДО НАШОЇ КОМАНДИ

Найпоширеніші вразливості в Python-коді

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

Використання eval() та інші небезпечні конструкції

Функція eval() дозволяє виконувати код з рядків, що створює серйозні ризики. Якщо рядок, переданий у eval(), надходить із ненадійного джерела (наприклад, від користувача), зловмисники можуть виконати небезпечні операції, сформувавши шкідливі вирази.

Розгляньмо простий приклад:

user_input = input("Введіть вираз: ")

eval(user_input)  # НЕБЕЗПЕЧНО!

Якщо користувач введе __import__('os').system('rm -rf /'), цей код видалить системні файли. Надмірне використання eval() також знижує читабельність і супроводжуваність коду, оскільки IDE та інструменти статичного аналізу не можуть ефективно перевіряти такий код.

Безпечною альтернативою є використання json.loads() для розбору даних або ast.literal_eval() для безпечного оцінювання літералів. Важливо розуміти, що json.loads() не є прямою заміною eval() і не призначений для виконання коду. Часто eval() застосовують не для виконання коду, а для «швидкого парсингу» рядків, але це пов’язано з ризиками, які слід враховувати. Тому варто обирати інструменти відповідно до конкретних завдань і уникати зайвих спрощень.

Відсутність перевірки типів у динамічному коді

Функції без явних типових анотацій у Python динамічно типізовані, і mypy не перевіряє їх. Коли код занадто складний для mypy, ви можете зробити змінну або параметр динамічно типізованими, явно надавши їм тип Any.

Використання Any фактично локально вимикає перевірку типів і дозволяє:

  • призначати значення типу Any змінній будь-якого типу;
  • виконувати будь-які операції над значенням;
  • змінювати тип змінної довільним чином.

Параметри функцій без анотацій також неявно мають тип Any.

Неправильна обробка винятків

Некоректне поводження з винятками — одна з найпоширеніших проблем, що може призвести до серйозних вразливостей. Поганою практикою вважається перехоплення всіх винятків одним блоком except Exception або простим except.

Поєднання try, except і pass дозволяє програмі продовжувати роботу без обробки винятку:

try:

    linux_interaction()

except:

    pass  # Небезпечний підхід

Пропускання винятків без належної обробки є поганою практикою. Якщо блок try-catch приховує помилки без належного логування, це може маскувати вразливості, особливо в процесах автентифікації.

Використання застарілих або вразливих бібліотек

Застарілі або вразливі бібліотеки становлять серйозну загрозу безпеці Python-додатків. Більшість Python-пакетів публікуються на PyPI без жодної перевірки безпеки. Це означає, що зловмисники можуть легко створити пакет зі шкідливим кодом або опублікувати пакет з назвою, схожою на популярний.

Атаки на бібліотеки PyPI включають:

  • спам-пакети;
  • тайпосквотинг (typosquatting) із шкідливим ПЗ;
  • пакети, призначені для крадіжки облікових даних розробників.

Наприклад, пакет Ascii2text намагався імітувати популярний пакет art, одночасно завантажуючи шкідливий скрипт, що шукав локальні паролі та експортував їх через вебхук Discord.

Крім того, розробники іноді забувають оновлювати бібліотеки після випуску патчів, що залишає додаток вразливим до відомих атак.

Статичний та динамічний аналіз Python-коду

Для виявлення вразливостей у коді Python використовують два основні підходи: статичний та динамічний аналіз. Ці методи доповнюють один одного, дозволяючи знаходити різні типи помилок на різних етапах розробки.

Статичний аналіз: Pylint, MyPy, Bandit

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

Популярні інструменти статичного аналізу для Python включають:

  • pylint — перевіряє дотримання стандартів кодування та виявляє потенційні помилки;
  • mypy — забезпечує статичну перевірку типів, допомагаючи виявити помилки на ранніх стадіях розробки;
  • bandit — спеціалізований інструмент для виявлення вразливостей безпеки, як-от жорстко закодовані паролі, ін'єкції shell та SQL.

Динамічний аналіз: інтеграція з pytest та coverage

Динамічний аналіз досліджує програму під час її виконання, виявляючи помилки та вразливості, які проявляються лише в реальному середовищі. Цей підхід особливо корисний для знаходження складних дефектів, причини яких занадто складні для виявлення статичним аналізом.

Для Python поширеним інструментом динамічного аналізу є coverage.py, який вимірює, яка частина коду була виконана під час тестування. Він інтегрується з pytest через плагін pytest-cov. За потреби coverage.py можна налаштувати на використання dynamic context, щоб розмежовувати виконання коду між окремими тестами.

Команда pytest --cov=my_module . запускає тести з вимірюванням покриття коду, а подальша команда coverage html створює звіт у форматі HTML для перегляду результатів.

Порівняння підходів: коли і що використовувати

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

Динамічний аналіз, навпаки, виявляє проблеми, що виникають виключно в робочому середовищі: джерела пам’яті, проблеми з продуктивністю та помилки під час виконання. Однак він охоплює лише ті шляхи, які були фактично виконані під час тестування.

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

Інтеграція інструментів безпеки в CI/CD

Інтеграція інструментів безпеки в процеси розробки через CI/CD дозволяє автоматизувати виявлення вразливостей та захистити код від потенційних загроз. Розгляньмо найефективніші методи такої інтеграції для забезпечення надійного захисту Python-проєктів.

Налаштування Bandit у GitHub Actions

Bandit — це потужний інструмент для виявлення вразливостей у Python-коді, який легко інтегрується з GitHub Actions. Рекомендується використовувати Python версії не нижче 3.8, що відповідає реальним рекомендаціям проєкту Bandit. Для налаштування потрібно створити файл конфігурації у директорії .github/workflows/:

name: Security Scan

on: [push, pull_request]

jobs:

  bandit-scan:

    runs-on: ubuntu-latest

    steps:

      - name: Checkout Code

        uses: actions/checkout@v4

      - name: Set Up Python

        uses: actions/setup-python@v3

        with:

          python-version: 3.8

      - name: Install Bandit

        run: pip install bandit

      - name: Run Bandit Scan

        run: bandit -ll -ii -r . -f json -o bandit-report.json

Важливо зазначити, що в реальних проєктах перед запуском Bandit зазвичай додають залежності проєкту, щоб інструмент міг коректно аналізувати імпорти. Без цього Bandit може неправильно інтерпретувати частини коду, видавати хибні спрацьовування або пропускати потенційно небезпечні ділянки.

Ключові параметри Bandit включають:

  •  -ll – встановлює рівень логування, відображаючи лише критичні проблеми;
  • -ii – встановлює рівень впевненості, звітуючи лише про високоймовірні вразливості;
  • -r . – рекурсивне сканування поточної директорії та її піддиректорій.

Використання Semgrep для політик безпеки

Semgrep – інструмент статичного аналізу, що дозволяє створювати та застосовувати політики безпеки для Python-коду. Він має понад 2500 правил у реєстрі та підтримує аналіз фреймворків Django, Flask і FastAPI.

Semgrep ідентифікує мови програмування у ваших репозиторіях і запускає лише ті правила, які застосовні до цих мов. Наприклад, додаючи правила для Ruby та Python, ви не вплинете на швидкість сканування Python-проєктів.

Політики безпеки можна налаштовувати для:

  • блокування злиття PR/MR при виявленні критичних вразливостей;
  • автоматичного додавання коментарів до коду з виявленими проблемами;
  • моніторингу вразливостей без блокування розробки.

Автоматичне форматування з Black у пайплайні

Інтеграція форматера Black у CI/CD пайплайни забезпечує уніфікований стиль коду та виключає стилістичні помилки. Однак, краще використовувати Black для перевірки форматування, а не для автоматичного виправлення коду безпосередньо в CI/CD:

name: Lint

on: [push, pull_request]

jobs:

  lint:

    runs-on: ubuntu-latest

    steps:

      - uses: actions/checkout@v2

      - uses: actions/setup-python@v2

      - name: Install Black

        run: pip install black

      - name: Check Formatting

        run: black --check --verbose --diff --color .

Такий підхід змусить розробників форматувати код перед відправкою через повідомлення про помилку при невідповідності стандартам. Зрештою, інтеграція інструментів безпеки в CI/CD дозволяє виявляти проблеми на ранніх етапах розробки та забезпечувати високу якість коду без додаткових зусиль команди.

Комбінування інструментів для повного покриття

Ефективність захисту Python-коду залежить від правильного підбору та комбінування інструментів безпеки. Отже, замість використання одного універсального рішення, варто створити систему взаємодоповнюючих засобів.

Ролі кожного інструменту: лінтери, сканери, форматери

Призначення конкретних ролей інструментам підвищує ефективність аналізу коду.

Зокрема:

  • лінтери (Flake8) для перевірки форматування та стилю;
  • сканери безпеки (Bandit, Jit) для виявлення ризиків;
  • глибокі аналізатори (SonarQube) для перевірки підтримуваності коду та виявлення «запахів коду».

Форматер Black забезпечує узгоджений стиль без компромісів, а Pylint виконує перевірку відповідності стандартам програмування та виділяє помилки.

Уникнення дублювання помилок у звітах

Для запобігання втоми від сповіщень важливо:

  • налаштувати фільтри серйозності помилок;
  • використовувати спеціальні набори правил;
  • створювати проєктно-орієнтовані конфігурації.

У логерах Python можна запобігти дублюванню, встановивши параметр propagate: False.

Використання агрегаторів результатів (наприклад, SonarQube)

SonarQube — потужна платформа, що забезпечує постійний аналіз якості коду та виявляє:

  • вразливості безпеки;
  • «запахи коду»;
  • потенційні помилки.

Вона пропонує детальні звіти та панелі з інформацією про якість коду. Альтернативно SimpleSecurity об'єднує результати кількох інструментів безпеки Python і створює звіти у різних форматах — JSON, Markdown, CSV та SARIF.

Висновок

Отже, безпека Python-коду безперечно стає критичним фактором у світі, де кіберзагрози зростають експоненційно. Протягом статті ми розглянули найпоширеніші вразливості, включаючи необережне використання функції eval(), відсутність перевірки типів, неправильну обробку винятків та застосування застарілих бібліотек. Кожна з цих проблем може відкрити «двері» для потенційних атак.

Безумовно, найефективніший підхід до захисту вашого коду — поєднання статичного та динамічного аналізу. Статичні інструменти, як-от Pylint, MyPy та Bandit, виявляють проблеми без запуску програми, тоді як динамічні засоби знаходять вразливості під час виконання коду. Така комбінація забезпечує всебічний захист вашого Python-проєкту.

Інтеграція інструментів безпеки в CI/CD пайплайни натомість автоматизує процес виявлення вразливостей. Завдяки налаштуванню Bandit у GitHub Actions, застосуванню Semgrep для політик безпеки та використанню Black для форматування коду ви значно підвищуєте захищеність вашого проєкту без додаткових зусиль команди.

Зрештою, для досягнення повного покриття важливо комбінувати різні інструменти, призначаючи їм конкретні ролі:

  • лінтери для перевірки стилю;
  • сканери для виявлення ризиків безпеки;
  • аналізатори для оцінки підтримуваності коду.

Таким чином, захист Python-коду — це не одноразова дія, а безперервний процес. Впровадження описаних у цій статті підходів допоможе вам створювати безпечні вебдодатки та ефективно протистояти кіберзагрозам що постійно еволюціонують. Адже в сучасному цифровому світі безпека перестала бути додатковою опцією — вона стала необхідною умовою життєздатності будь-якого програмного продукту.

ДАЛІ МОЖНА ПОЧИТАТИ

Підписатися на новини

Чудово! Ми вже готуємо добірку актуальних новин для вас :)

Вибачте, щось пішло не так. Будь ласка, спробуйте ще раз.

* Обов'язкові поля

*Будь ласка, заповніть обов’язкові поля