Добавить в корзинуПозвонить
Найти в Дзене
test_chanel

🔓 Padding Oracle: Когда криптография работает против вас

Часто разработчики думают: «Я зашифрую ID объекта в URL, и никто не узнает, что там внутри». Они берут AES, режим CBC и считают задачу выполненной. Но без одной важной детали эта защита превращается в дыру, через которую можно не только читать, но и подделывать данные. Разбираем мой недавний кейс. 🎯 Суть уязвимости Сервер использует AES-CBC для шифрования идентификаторов форм. Проблема в двух фатальных ошибках реализации: Болтливый сервер: Если при дешифровке паддинг некорректен, сервер возвращает ошибку crypto: padding: bad padding. Нет подписи: Отсутствует HMAC. Сервер пытается расшифровать любые пришедшие данные. Это классическая атака Padding Oracle. 🧠 Как это работает В AES-CBC данные делятся на блоки. Последний блок всегда должен иметь правильный «хвост» (паддинг), чтобы длина была кратна размеру блока. Атака выглядит так: Мы берем зашифрованную строку. Мы меняем один байт в шифротексте и отправляем серверу. Сервер пытается расшифровать. Из-за особенностей CBC, где блок XOR

🔓 Padding Oracle: Когда криптография работает против вас

Часто разработчики думают: «Я зашифрую ID объекта в URL, и никто не узнает, что там внутри». Они берут AES, режим CBC и считают задачу выполненной. Но без одной важной детали эта защита превращается в дыру, через которую можно не только читать, но и подделывать данные.

Разбираем мой недавний кейс.

🎯 Суть уязвимости

Сервер использует AES-CBC для шифрования идентификаторов форм.

Проблема в двух фатальных ошибках реализации:

Болтливый сервер: Если при дешифровке паддинг некорректен, сервер возвращает ошибку crypto: padding: bad padding.

Нет подписи: Отсутствует HMAC. Сервер пытается расшифровать любые пришедшие данные.

Это классическая атака Padding Oracle.

🧠 Как это работает

В AES-CBC данные делятся на блоки. Последний блок всегда должен иметь правильный «хвост» (паддинг), чтобы длина была кратна размеру блока.

Атака выглядит так:

Мы берем зашифрованную строку.

Мы меняем один байт в шифротексте и отправляем серверу.

Сервер пытается расшифровать. Из-за особенностей CBC, где блок XOR-ится с предыдущим, изменение байта в шифротексте ломает данные в текущем блоке, но предсказуемо меняет данные в следующем.

Момент истины:

Если паддинг после расшифровки сломался -> Сервер кричит: «Bad padding!».

Если паддинг случайно совпал (например, 0x01) -> Сервер выдает другую ошибку, но не ошибку паддинга.

Это и есть Оракул. Сервер говорит нам: «Твоя догадка верна» или «Нет, пробуй дальше». Перебирая по 256 вариантов на байт, мы можем побайтово расшифровать любой ID.

🔥 Импакт: Почему это критично?

Мы не просто читаем скрытые ID. Мы можем шифровать свои данные.

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

Хочешь попасть в форму администратора с ID 0001?

Подделываешь шифротекст через Оракула.

Сервер расшифровывает его, видит 0001 и пускает тебя.

В данном кейсе это привело к доступу к скрытым тестовым формам и интерфейсам, которые не должны быть публичными.

🛠 Как искать такое?

Fuzzing параметров: Видите длинные base64/hex строки в URL на подобие: 14c0555e4074d652ccb6690a104cc30063? Попробуйте изменить пару байт.

Смотрите на ответы: Если сервер возвращает разные HTTP-коды или сообщения об ошибках для «просто битых данных» и «ошибок криптографии» — вы нашли золото.

Инструменты: PadBuster (в моём кейсе оказался бесполезным) или самописные скрипты на Python (как я и сделал), так как тайминги и формат ошибок везде разные.

🛡 Как фиксить

Encrypt-then-MAC: Всегда используйте HMAC для проверки целостности перед попыткой дешифровки.

Используйте AEAD: Переходите на AES-GCM или ChaCha20-Poly1305. Они проверяют целостность "из коробки".

Generic Errors: Никогда не показывайте детали криптографических ошибок. Возвращайте одинаковый ответ для любого сбоя дешифровки.

Вот код на пайтон, который я использовал для подмены ID:

import binascii

def forge_id(target_plaintext, intermediate_hex, original_ciphertext_hex):

padding_len = 16 - len(target_plaintext)

padding = bytes([padding_len] * padding_len)

target_block = target_plaintext.encode() + padding

intermediate = binascii.unhexlify(intermediate_hex)

original_cipher = binascii.unhexlify(original_ciphertext_hex)

new_iv = bytes([intermediate[i] ^ target_block[i] for i in range(16)])

forged_id = "14" + binascii.hexlify(new_iv).decode() + original_ciphertext_hex

return forged_id

INTERMEDIATE = "383936350c0c0c0c0c0c0c0c0c0c0c0c"

ORIGINAL_CIPHER = "c0555e4074d652ccb6690a104cc30063"

# Past your ID here

target = "0003"

forged = forge_id(target, INTERMEDIATE, ORIGINAL_CIPHER)

print(f"[*] Target Plaintext: {target}")

print(f"[*] Generated Forged ID: {forged}")

print(f"[*] Payload length: {len(forged)} chars (should be 66)")