
🔪 CRIME attack PoC : compression oracle атакует CVE-2012-4929 🔪
CRIME-атака: атака на оракул сжатия CVE-2012-4929, обнаруженная Жулиано Риццо и Таем Дуонгом;
При атаке на оракул сжатия использование адаптивного сжатия данных для смеси выбранного открытого текста и неизвестного открытого текста может приводить к изменениям длины сжатого текста, зависящим от содержимого, которые можно обнаружить, даже если сам сжатый текст затем шифруется. Это может использоваться в протокольных атаках для обнаружения случаев, когда внедрённый известный открытый текст хотя бы частично похож на неизвестное содержимое секретной части сообщения, что значительно снижает сложность поиска совпадения для секретного текста. Атаки CRIME и BREACH являются примерами протокольных атак, использующих это явление.
Многие статьи объясняют, как работает атака CRIME, но вот лучшие объяснения, которые я нашёл в интернете:
Эта атака на самом деле несложная, но по-настоящему интересная часть — реализация, которая немного отличается от «теории».
Давайте посмотрим на наивный метод, описанный в статье; это хороший способ понять, как он работает:
Атакующий может контролировать запрос, отправляемый клиентом (например, с помощью JavaScript). Цель — получить секретную cookie. Атакующий отправляет несколько таких запросов и проверяет длину зашифрованных данных:
| Запрос | длина |
|---|---|
| GET /cookie= DATA cookie=quokkalight | 80 |
| GET /cookie=a DATA cookie=quokkalight | 81 |
| GET /cookie=b DATA cookie=quokkalight | 81 |
| GET /cookie=. DATA cookie=quokkalight | 81 |
| GET /cookie=q DATA cookie=quokkalight | 80 |
Поскольку cookie=q совпадает с cookie=quokkalight из секретной cookie, длина зашифрованных данных будет одинаковой, и атакующий понимает, что он нашёл один байт.
Но этот метод иногда даёт сбои и ему нельзя доверять, поэтому вместо него мы используем другой метод.
Сначала мы отправляем запрос с искомым символом, за которым следуют несколько символов, которые не могут быть найдены в исходном запросе, например специальные символы: chr(i) + "#:/[@/&". Затем мы отправляем второй запрос, но инвертируем полезную нагрузку: "#:/[@/&" + chr(i) и сравниваем две длины. Если len(enc(req1)) < len(enc(req2)), значит, мы нашли байт. Этот метод называется two_tries, и он гораздо надёжнее:
| Запрос для поиска байта q | длина |
|---|---|
| GET /cookie=a~#:/[@/& DATA cookie=quokkalight | 81 |
| GET /cookie=~#:/[@/&a DATA cookie=quokkalight | 81 |
| GET /cookie=b~#:/[@/& DATA cookie=quokkalight | 81 |
| GET /cookie=~#:/[@/&b DATA cookie=quokkalight | 81 |
| GET /cookie=q~#:/[@/& DATA cookie=quokkalight | 80 |
| GET /cookie=~#:/[@/&q DATA cookie=quokkalight | 81 |
Атакующий нашёл один байт!
if len(enc(request1)) < len(enc(request2)):
print("found byte")
Реализованный мной метод two_tries полностью рекурсивен, но почему? Иногда можно найти более одного байта, потому что сжатие совпадает с несколькими шаблонами.
Возьмём секрет: cookie=quokkalight. Если запустить алгоритм two_tries, мы получим следующий результат:
result 1: cookie=quokie=quokie=quokie=quokie=quokie=
result 2: cookie=quokkalight
Алгоритм должен пройти все пути в дереве, чтобы найти все возможные решения. Все результаты можно представить в виде дерева, как показано ниже:

Доказательство концепции атаки CRIME против потокового шифра можно найти в файле: CRIME-RC4-poc.py. Это реализация на Python предыдущего объяснения.
python3 CRIME-RC4-poc.py
Полная демонстрация и результат:
Когда используется режим CBC с AES или DES, атака не так проста, как с RC4. Поскольку всё делится на блоки, атака немного сложнее (но не слишком).
Например, если мы используем AES в режиме CBC, блок будет разделён на части длиной 16, а в конец будет добавлен padding, если len(data)%16 != 0.
В режиме CBC важно отметить, что полезная нагрузка payload=rand даст длину 16, а не 12, поскольку padding будет добавлен в конец данных до шифрования. Поэтому наша предыдущая атака не сработает.
Пример:
| блок1 | блок2 | блок3 | длина |
|---|---|---|---|
| GET /cookie= DA | TA cookie=quokka | light + PAD(11) | 48 |
| GET /cookie=a DA | TA cookie=quokka | light + PAD(10) | 48 |
| GET /cookie=b DA | TA cookie=quokka | light + PAD(10) | 48 |
| GET /cookie=. DA | TA cookie=quokka | light + PAD(10) | 48 |
| GET /cookie=q DA | TA cookie=quokka | light + PAD(11) | 48 |
В этом примере длина всегда будет одинаковой, потому что при добавлении или удалении байта длина всегда будет той же — изменится только padding. Атакующий видит только зашифрованные данные, и у него нет способа узнать padding по зашифрованным данным.
Решение: сыграть на спецификации режима CBC, чтобы длина padding стала равной 1, добавив случайное значение в переменную GARB (в параметр GET, поскольку атакующий контролирует данные GET и POST).
| блок1 | блок2 | блок3 | блок4 | длина |
|---|---|---|---|---|
| GET /GARBc | ookie= DATA coo | kie=quokkalight + PAD(1) | 48 | |
| GET /GARBc | ookie=a DATA coo | kie=quokkalight | PAD(16) | 64 |
| GET /GARBc | ookie=b DATA coo | kie=quokkalight | PAD(16) | 64 |
| GET /GARBc | ookie=. DATA coo | kie=quokkalight | PAD(16) | 64 |
| GET /GARBc | ookie=q DATA coo | kie=quokkalight + PAD(1) | 48 |
Если байт совпадает с шаблоном, длина будет одинаковой; если не совпадает, длина будет другой.
Далее нам просто нужно использовать тот же метод, что описан в части про RC4, добавив лишь один шаг перед этим.
adjust_padding(), чтобы получить padding длиной 1two_tries_recursive() и можем найти секретный FLAG!python3 CRIME-cbc-poc.py
совсем скоро...
\ /
\ _ /
----/_\----
x--------------( . )--------------x
x|x | |_|\_/|_| | x|x
x x x x
https://www.nccgroup.trust/globalassets/our-research/us/whitepapers/ssl_attacks_survey.pdf https://github.com/cloudflare/cf-nocompress https://www.ekoparty.org/archive/2012/CRIME_ekoparty2012.pdf https://security.stackexchange.com/questions/19911/crime-how-to-beat-the-beast-successor/19914#19914