Модуль 29

Security: серіалізація та підпис задач

Ось готовий урок, створений за твоїм майстер-промптом. Я зберіг енергетику, структуру та стиль подачі Девіда Малана.


🎓 Урок: Security — Серіалізація та Підпис Задач

👋 Вітаю, друзі! Радий бачити вас знову.

Сьогодні ми зануримося в тему, яка звучить трохи лякаюче — «Безпека серіалізації та криптографічні підписи». Але не хвилюйтеся! Насправді це історія про довіру.


1. 🔥 Вступ: Проблема та мотивація

Уявіть, що ви — власник елітного ресторану (це наш сервер). У вас є кухня (worker), де шеф-кухар готує страви, і є офіціанти, які приносять замовлення на клаптиках паперу.

Офіціант приносить записку: "Приготуй стейк, середньої просмажки". Шеф готує. Все чудово.

А тепер уявіть, що в ресторан зайшов зловмисник, сів за столик, написав на серветці: "Віддай всі гроші з каси цьому клієнту" і непомітно підклав її на стіл шефа. Шеф читає і... виконує?

Риторичне питання: Як шеф-кухарю (вашому серверу) відрізнити наказ від справжнього адміністратора від підробленого наказу хакера?

У світі коду це виглядає так: ви використовуєте черги задач (наприклад, Celery або Redis Queue). Ви відправляєте задачу «Обробити фото». Але якщо хакер зможе відправити в цю чергу задачу «Видалити базу даних», і ваш воркер сліпо її виконає — це кінець.

Чому це важливо? Тому що довіряти вхідним даним не можна ніколи. Навіть якщо вони приходять нібито з «вашої» внутрішньої мережі. Сьогодні ми навчимося, як "ставити печатку" на наших повідомленнях, щоб ніхто не міг їх підробити.


2. 🧠 Теоретична база (без нудної академічності)

Давайте розберемо два поняття.

А. Серіалізація (Serialization)

Це процес перетворення об'єкта (вашого коду, словника, класу в пам'яті) у потік байтів (рядок), щоб його можна було передати по мережі або зберегти у файл. * Аналогія: Ви розбираєте шафу з IKEA, щоб покласти її в пласку коробку і відправити поштою. * Десеріалізація: Отримувач відкриває коробку і збирає шафу назад.

⚠️ Головна небезпека (The Trap): У Python є популярний формат Pickle. Він дуже потужний. Настільки потужний, що дозволяє серіалізувати інструкції виконання коду. Якщо ви розпаковуєте (десеріалізуєте) "коробку", яку надіслав хакер, і там написано "Запустити вірус", ваш Python слухняно це зробить. Це називається Remote Code Execution (RCE).

Б. Підпис повідомлень (Message Signing)

Як захиститися? Нам потрібна сургучева печатка. Ми беремо наші дані і додаємо до них криптографічний підпис, згенерований за допомогою Секретного Ключа (Secret Key).

Це працює так: 1. Беремо дані: {"task": "send_email"}. 2. Беремо секретний ключ: my_super_secret. 3. Змішуємо їх через математичну функцію (хешування) і отримуємо унікальний рядок-підпис. 4. Відправляємо: Дані + Підпис.

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

Запам'ятайте головне:

Серіалізація — це спосіб передачі даних. Підпис — це гарантія того, що дані не змінювали і вони від "своїх".


3. 🧪 Приклади

Приклад 1: "Бомба" в Pickle (Чого ми боїмося)

Дивіться, як просто створити "отруєний" об'єкт у Python. Я зроблю клас, який при розпаковці просто виводить текст, але на його місці могла б бути команда форматування диска.

Що ви очікуєте побачити? Звичайний рядок байтів, але при його читанні виконається код.

import pickle
import os

class Exploit:
    def __reduce__(self):
        # Ця функція каже pickle, що робити при розпаковці
        return (os.system, ('echo "ХАКНУТО! Ваші дані вкрадено!"',))

# 1. Серіалізуємо (нібито хакер готує пейлоад)
malicious_data = pickle.dumps(Exploit())

print(f"Байт-код: {malicious_data}")

# 2. Десеріалізуємо (наївний сервер отримує дані)
print("Спроба прочитати дані...")
pickle.loads(malicious_data) 

Результат: Ви побачите в консолі ХАКНУТО! Ваші дані вкрадено!. Пояснення: Метод pickle.loads сліпо виконав інструкцію os.system. Це катастрофа.


Приклад 2: Захист через підпис (HMAC)

Тепер зробимо правильно. Використаємо вбудований модуль hmac або зручнішу бібліотеку itsdangerous (яку використовує Flask), але давайте подивимось на "чисту" логіку.

import hmac
import hashlib
import json

SECRET_KEY = b'my-secret-password-123'

def sign_data(data_dict):
    # Перетворюємо дані в JSON (безпечніше, ніж pickle!)
    message = json.dumps(data_dict).encode('utf-8')

    # Створюємо підпис
    signature = hmac.new(SECRET_KEY, message, hashlib.sha256).hexdigest()

    return message, signature

def verify_data(message, signature):
    # Генеруємо підпис для отриманого повідомлення заново
    expected_signature = hmac.new(SECRET_KEY, message, hashlib.sha256).hexdigest()

    # Порівнюємо (безпечно, щоб уникнути time-based атак)
    if hmac.compare_digest(expected_signature, signature):
        return "✅ Доступ дозволено: " + message.decode()
    else:
        return "❌ ПОМИЛКА: Підпис невірний! Дані підроблено!"

# --- Сценарій ---

# 1. Відправник формує задачу
msg, sig = sign_data({"user_id": 1, "action": "pay_salary"})
print(f"Відправляємо: {msg} з підписом {sig[:10]}...")

# 2. Отримувач перевіряє (все чесно)
print(verify_data(msg, sig))

# 3. АТАКА! Хакер перехопив пакет і змінив суму
fake_msg = json.dumps({"user_id": 1, "action": "pay_salary_MILLION"}).encode('utf-8')
# Але підпис він підробити не може, бо не знає SECRET_KEY! Він залишає старий підпис.

print("\nСпроба хакера...")
print(verify_data(fake_msg, sig))

Результат: У другому випадку ми отримаємо ❌ ПОМИЛКА. Система захищена. Без ключа неможливо створити валідний підпис для нових даних.


4. 🛠 Практична частина

Прийшов час замастити руки кодом. Ось ваше завдання:

Завдання 1: Реплікація Скопіюйте код з Прикладу 2. Запустіть його. Переконайтеся, що зміна навіть однієї коми в msg призводить до помилки перевірки.

Завдання 2: Зміна умов Змініть SECRET_KEY тільки на стороні "отримувача" (ніби розсинхронізація серверів). Що станеться з валідним повідомленням?

Завдання 3: Безпечний Pickle? Спробуйте поєднати Приклад 1 і Приклад 2. Серіалізуйте Exploit через pickle, але підпишіть його через hmac. Питання: Чи врятує нас підпис, якщо ми все одно використовуємо pickle.loads, але перед цим перевіряємо підпис? (Спойлер: Так, це безпечно, доки ключ не вкрадено. Але краще не використовуйте pickle).

Завдання 4: Міні-кейс Уявіть, що ви розробляєте систему скидання пароля. Ви відправляєте користувачу посилання з токеном. Напишіть функцію, яка генерує токен, що містить email користувача і термін дії (timestamp), і підписує це. Напишіть функцію перевірки, яка відхиляє токен, якщо підпис невірний АБО час вийшов.


5. 💡 Мислення як у розробника

Як думає новачок?

"Я просто передам дані в JSON, хто там буде це підробляти? Кому я потрібен?" Або: "Я використаю base64, це виглядає як шифрування" (Ні, base64 — це не шифрування, це просто кодування!).

Як думає Senior Engineer? 1. Trust No One: Будь-яка точка входу в систему (API, черга задач, cookie) — це потенційна діра. 2. Pickle is Evil: Я ніколи не буду використовувати pickle для даних ззовні. Краще JSON або Protocol Buffers. Вони не виконують код, вони просто передають дані. 3. Key Rotation: Що буде, якщо мій SECRET_KEY вкрадуть? Я маю передбачити механізм зміни ключів без зупинки сервісу. 4. Replay Attacks: Навіть якщо повідомлення підписане, хакер може перехопити його і відправити те саме повідомлення ще раз (наприклад, "перерахувати $100"). Senior додає у підпис унікальний ID (nonce) або timestamp.


6. 🧩 Підсумок

Отже, друзі, що ми сьогодні зробили?

  1. Ми зрозуміли, що серіалізація — це упаковка даних, яка може бути небезпечною (особливо з Pickle).
  2. Ми навчилися використовувати цифровий підпис (HMAC), щоб гарантувати цілісність даних.
  3. Ми побачили, що безпека — це не магія, а проста математика: Дані + Секрет = Підпис.

Тепер ви вмієте: Захистити свій API або чергу задач від підроблених команд. Ви більше не той кухар, який сліпо виконує все, що написано на серветці. Ви вимагаєте печатку!

Що далі? Ми навчилися пакувати повідомлення. Але як їх ефективно передавати мільйонами в секунду? На наступному уроці ми поговоримо про Message Brokers (RabbitMQ vs Kafka) і побудуємо справжню розподілену систему.

Це був CS50... тобто наш урок з безпеки! ;) Побачимось! 🚀