Привет, не знаю зайдет ли такое, но го попробуем, разберём атаку которую обожают CTF-авторы и которую до сих пор встречают в проде - Padding Oracle.
Суть в том, что если сервер по-разному реагирует на "правильный паддинг" и "неправильный паддинг" - мы можем вытащить открытый текст из шифртекста вообще без знания ключа.
Если кто-то не знает, то вот краткая теория:
AES-CBC шифрует данные блоками по 16 байт. Чтобы данные всегда делились ровно на 16, используется PKCS#7 паддинг: если не хватает N байт - добиваем N байтами со значением N.
При расшифровке сервер проверяет этот паддинг. И вот тут появляется проблема.
Расшифровка в CBC работает так:
Промежуточное значение AES_decrypt(ct_block) - назовём его intermediate -нам неизвестно. Но мы можем его вычислить, если можем задавать ct_block[i-1] и смотреть что отвечает сервер.
Я написал уязвимый сервер и скрипт атаки на этот сервер
server.py - HTTP сервер с двумя эндпоинтами:
GET /token - выдаёт зашифрованную куку admin=false;user=guest
GET /check?token=HEX - проверяет куку и вот здесь живёт оракул
Атакуем один блок шифртекста. Подбираем последний байт:
Повторяем для каждого байта, меняя целевой паддинг (для второго байта с конца добиваемся \x02\x02, и так далее).
После запуска нашего скрипта атаки все прошло как надо
Ключ мы так и не узнали. Но открытый текст - наш.
ЧТо имеем в скрипте атаки
Максимум запросов: 256 × 16 × N_блоков. Для двух блоков ~8192 запроса. На локалке занимает секунды.
Что имеем в реальности, где это еще можно увидеть
Классика - ASP.NET ViewState, ну раньше так точно было, щас уже врятли актуально
Там был тот же принцип: сервер отдавал разные ошибки на кривой паддинг и нормальный, и исследователи Rizzo/Duong написали тул POET который автоматизировал атаку.
В CTF встречается регулярно - ищите задачи где есть "зашифрованная сессия" и сервер отвечает по-разному на плохой токен.
Защита
Одна строчка решает проблему - используйте AES-GCM вместо AES-CBC:
GCM проверяет целостность до расшифровки. Если шифртекст тронут - ошибка одна и та же, без утечки о паддинге.
З.Ы. Тапками сильно не кидайте, опыт написания статей ~0
Суть в том, что если сервер по-разному реагирует на "правильный паддинг" и "неправильный паддинг" - мы можем вытащить открытый текст из шифртекста вообще без знания ключа.
Если кто-то не знает, то вот краткая теория:
AES-CBC шифрует данные блоками по 16 байт. Чтобы данные всегда делились ровно на 16, используется PKCS#7 паддинг: если не хватает N байт - добиваем N байтами со значением N.
Code:
"hello" → "hello\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
↑ 11 байт паддинга, каждый = 0x0b
При расшифровке сервер проверяет этот паддинг. И вот тут появляется проблема.
Не удалось прогрузить медиа
Расшифровка в CBC работает так:
Code:
plaintext[i] = AES_decrypt(ct_block[i]) XOR ct_block[i-1]
Промежуточное значение AES_decrypt(ct_block) - назовём его intermediate -нам неизвестно. Но мы можем его вычислить, если можем задавать ct_block[i-1] и смотреть что отвечает сервер.
Не удалось прогрузить медиа
Я написал уязвимый сервер и скрипт атаки на этот сервер
server.py - HTTP сервер с двумя эндпоинтами:
GET /token - выдаёт зашифрованную куку admin=false;user=guest
GET /check?token=HEX - проверяет куку и вот здесь живёт оракул
Python:
def decrypt_and_check(token_hex):
try:
raw = bytes.fromhex(token_hex)
iv = raw[:16]
ct = raw[16:]
pt = unpad(AES.new(KEY, AES.MODE_CBC, iv).decrypt(ct), 16)
return True, pt.decode()
except ValueError:
return False, "Invalid padding" # <- утечка информации
Не удалось прогрузить медиа
Как работает атака
Атакуем один блок шифртекста. Подбираем последний байт:
- Берём предыдущий блок (prev_block), делаем его копию crafted_prev
- Меняем последний байт crafted_prev[15] перебором от 0 до 255
- Отправляем на сервер crafted_prev + ct_block
- Когда сервер вернёт 200 - паддинг \x01 валидный
- Значит: crafted_prev[15] XOR intermediate[15] == 0x01
- Раскрываем: intermediate[15] = crafted_prev[15] XOR 0x01
- Открытый текст: plaintext[15] = intermediate[15] XOR original_prev[15]
Повторяем для каждого байта, меняя целевой паддинг (для второго байта с конца добиваемся \x02\x02, и так далее).
Не удалось прогрузить медиа
После запуска нашего скрипта атаки все прошло как надо
Не удалось прогрузить медиа
Ключ мы так и не узнали. Но открытый текст - наш.
ЧТо имеем в скрипте атаки
Не удалось прогрузить медиа
Максимум запросов: 256 × 16 × N_блоков. Для двух блоков ~8192 запроса. На локалке занимает секунды.
Что имеем в реальности, где это еще можно увидеть
Классика - ASP.NET ViewState, ну раньше так точно было, щас уже врятли актуально
Там был тот же принцип: сервер отдавал разные ошибки на кривой паддинг и нормальный, и исследователи Rizzo/Duong написали тул POET который автоматизировал атаку.
В CTF встречается регулярно - ищите задачи где есть "зашифрованная сессия" и сервер отвечает по-разному на плохой токен.
Защита
Одна строчка решает проблему - используйте AES-GCM вместо AES-CBC:
Python:
# Уязвимо
AES.new(key, AES.MODE_CBC, iv).encrypt(pad(data, 16))
# Безопасно — GCM аутентифицирует шифртекст, оракула нет
cipher = AES.new(key, AES.MODE_GCM)
ct, tag = cipher.encrypt_and_digest(data)
GCM проверяет целостность до расшифровки. Если шифртекст тронут - ошибка одна и та же, без утечки о паддинге.
З.Ы. Тапками сильно не кидайте, опыт написания статей ~0
👍23
😮6
💯4
LIMB