flawopen.com/Teardowns/cve-2014-0160-heartbleed
CVE-2014-0160: утечка памяти Heartbleed в OpenSSL
CVE-2014-0160 позволяла TLS- или DTLS-пиру читать до 64 КиБ за пределами записи Heartbeat, если OpenSSL доверял заявленной длине полезной нагрузки, которой в записи не было. В upstream-исправлении запись проверяется до разбора и возврата данных.[1], [2]
Представьте сотрудника, которому дали карточку с несколькими словами, но он поверил наклейке с просьбой сделать намного более длинную копию. Вместо того чтобы остановиться на краю карточки, он продолжает копировать текст из общего лотка. Heartbleed заставляла OpenSSL читать за пределами полученной записи; раскрытые байты зависели от содержимого памяти процесса.[1], [3]
Ключевые понятия и термины
Heartbeat в TLS/DTLS (RFC 6520)- Сообщение поддержания соединения в TLS или DTLS. Запрос содержит payload, который пир возвращает, а также требуемое протоколом дополнение.[3]
payload_length- 16-битное поле с размером payload Heartbeat. Он должен помещаться в полученную запись после типа сообщения, поля длины и минимального дополнения.[1], [3]
memcpy()- Копирует заданное число байтов начиная с указателя источника. Функция не знает допустимую длину исходного буфера; её должен проверить вызывающий код.[1], [2]
Чтение за границей буфера (CWE-126)- Чтение после конца предназначенного исходного буфера. При Heartbleed ответ мог раскрыть до 64 КиБ памяти процесса — в зависимости от её содержимого.[5]
Связи со стандартами
- Ссылка CWE для слабого места, описанного в этом руководстве: CWE-126
- CISA KEV — добавлено 2022-05-04 — CVE-2014-0160
Анализ первопричины
tls1_process_heartbeat() и dtls1_process_heartbeat() читали 16-битную длину полезной нагрузки Heartbeat и в пути ответа передавали её в memcpy(), не проверив, что столько байтов действительно есть в s->s3->rrec.length. Пир мог указать больше данных, чем содержала TLS- или DTLS-запись. Затем OpenSSL возвращала байты за пределами полученной записи и могла раскрыть до 64 КиБ памяти процесса; содержимое зависело от памяти, поэтому конкретный ключ, пароль или cookie не обязательно попадал в ответ. Upstream-коммит 96db902 сначала отбрасывает записи, слишком короткие для типа, поля длины и минимального дополнения, затем сравнивает 1 + 2 + payload + 16 с длиной полученной записи. Он также ограничивает ответ через SSL3_RT_MAX_PLAIN_LENGTH и использует вычисленный write_length. Аналогичные проверки внесены в обработчики TLS и DTLS.[1], [2], [3], [4]
Пошаговый сценарий атаки
Исходный код: Уязвимый vs Защищённый вариант
Это фрагменты upstream-изменения OpenSSL, а не самостоятельная программа. Уязвимый код разбирает поля до проверки наличия фиксированного заголовка и минимального дополнения, а затем копирует заявленную пиром длину payload. Исправление проверяет минимальный размер записи до разбора, сверяет заявленную полезную нагрузку с полученной записью и ограничивает размер ответа. OpenSSL внесла соответствующие изменения в tls1_process_heartbeat() и dtls1_process_heartbeat().[2], [1]
Фрагмент исходного кода: ssl/t1_lib.c
/* Фрагмент tls1_process_heartbeat() до исправления. */
hbtype = *p++;
n2s(p, payload);
pl = p;
/* В пути ответа TLS1_HB_REQUEST: */
memcpy(bp, pl, payload);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT,
buffer, 3 + payload + padding);
/* Отклоняет запись, слишком короткую для типа, длины и минимального заполнения. */
if (1 + 2 + 16 > s->s3->rrec.length)
return 0;
hbtype = *p++;
n2s(p, payload);
if (1 + 2 + payload + 16 > s->s3->rrec.length)
return 0;
pl = p;
/* В пути ответа TLS1_HB_REQUEST: */
unsigned int write_length = 1 + 2 + payload + padding;
if (write_length > SSL3_RT_MAX_PLAIN_LENGTH)
return 0;
buffer = OPENSSL_malloc(write_length);
memcpy(bp, pl, payload);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, write_length);
Хронология инцидента
Чек-лист по защите системы для инженеров
- ✓Обновитесь до поддерживаемой поставщиком версии OpenSSL с исправлением; upstream исправления выпущены в 1.0.1g и 1.0.2-beta2. Проверяйте backport дистрибутива, а не только отображаемый номер версии.[1], [2]
- ✓Для потенциально уязвимых конечных точек замените затронутые TLS-ключи и сертификаты и отзовите старые сертификаты, если утечка правдоподобна; учитывайте также клиенты, обрабатывавшие недоверенные heartbeat-сообщения.[6]
- ✓После установки исправления для уязвимой службы смените учетные данные, которые могли находиться в памяти ее процесса. Пользовательские пароли меняйте только после подтверждения владельца службы, что исправление установлено.[6], [8]
- ✓Проверьте TLS- и DTLS-обработчики на границах записи: слишком короткий фиксированный заголовок, длина payload больше полученных данных и корректные запросы с минимальным дополнением.[2], [3]
- ✓Если Heartbeat не нужен и совместимость это позволяет, пересоберите OpenSSL с
-DOPENSSL_NO_HEARTBEATS— альтернативной мерой из уведомления OpenSSL.[1]
Источники
- OpenSSL Security Advisory — TLS heartbeat read overrun (CVE-2014-0160)
- OpenSSL upstream fix — commit 96db902
- RFC 6520 — TLS and DTLS Heartbeat Extension
- NVD — CVE-2014-0160
- MITRE CWE-126 — Buffer Over-read
- Codenomicon / Heartbleed.com — The Heartbleed Bug
- CVE Program — CVE-2014-0160
- CISA — “Heartbleed” OpenSSL Vulnerability