Объяснение ошибки Heartbleed

Как вы, возможно, слышали, есть новая ошибка OpenSSL, и она плохая. Это не одна из тех ошибок или взломов, о которых вы слышите в новостях и игнорируете, как всегда. Это затрагивает около 66% всех интернет-серверов, которые, вероятно, включают веб-сайт, который вы часто посещаете или на котором есть конфиденциальная информация. Как это работает Итак, в чем именно заключается ошибка? Чтобы описать ошибку, нам нужно понять только несколько частей реализации OpenSSL. Приведенная ниже структура содержит протокол SSL.

Как вы, возможно, слышали, есть новая ошибка OpenSSL, и она плохая. Это не одна из тех ошибок или взломов, о которых вы слышите в новостях и игнорируете, как всегда. Это затрагивает около 66% всех интернет-серверов, которые, вероятно, включают веб-сайт, который вы часто посещаете или на котором есть конфиденциальная информация.

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

Так в чем именно заключается ошибка? Чтобы описать ошибку, нам нужно понять только несколько частей реализации OpenSSL. Структура ниже содержит запись SSL:

 typedef struct ssl3_record_st 
 { 
 /*r */ int type; /* type of record */ 
 /*rw*/ unsigned int length; /* How many bytes available */ 
 /*r */ unsigned int off; /* read/write offset into 'buf' */ 
 /*rw*/ unsigned char *data; /* pointer to the record data */ 
 /*rw*/ unsigned char *input; /* where the decode bytes are */ 
 /*r */ unsigned char *comp; /* only used with decompression - malloc()ed */ 
 /*r */ unsigned long epoch; /* epoch number, needed by DTLS1 */ 
 /*r */ PQ_64BIT seq_num; /* sequence number, needed by DTLS1 */ 
 /*rw*/ unsigned int orig_len; /* How many bytes were available before padding 
                was removed? This is used to implement the 
                MAC check in constant time for CBC records. 
                */ 
 } SSL3_RECORD; 

Эта структура используется в dtls1_process_heartbeat(SSL *s) в файле d1_both.c для обработки тактовых импульсов, которые поддерживают соединение SSL. Этот метод получает доступ к данным записи в структуре, как показано ниже:

 int 
 dtls1_process_heartbeat(SSL *s) 
 { 
 unsigned char *p = &s->s3->rrec.data[0], *pl; 
 unsigned short hbtype; 
 unsigned int payload; 
 unsigned int padding = 16; /* Use minimum padding */ 
 
 /* Read type and payload length first */ 
 hbtype = *p++; 
 n2s(p, payload); 
 pl = p; 

Первый байт записи SSL - это тип полученного пульса, а макрос n2s(c, l) просто берет два байта из p и помещает их в payload . Эти байты представляют собой длину полезной нагрузки в такте. Здесь важно отметить, что длина записи SSL не проверяется / не проверяется . Переменная pl теперь указывает на данные пульса, которые были отправлены клиентом.

Тот факт, что длина SSL-записи не проверялась, имеет большое значение, поскольку позже в той же функции память выделяется с использованием той же длины полезной нагрузки, как показано в приведенном ниже коде:

 unsigned char *buffer, *bp; 
 int r; 
 
 /* Allocate memory for the response, size is 1 byte 
 * message type, plus 2 bytes payload length, plus 
 * payload, plus padding 
 */ 
 buffer = OPENSSL_malloc(1 + 2 + payload + padding); 
 bp = buffer; 

Поскольку payload были назначены первые два байта данных контрольного сигнала, которые предоставляются клиентом , это означает, что оно может иметь максимальное значение 65535 (или 64 КБ, когда речь идет о байтах). По сути, мы выделяем тот объем памяти, который клиент хочет от нас (до 64 КБ), но в этом нет ничего страшного, верно? Выделение определяемых пользователем объемов данных само по себе не очень вредно (хотя это определенно нехорошо), но здесь это вредно, поскольку позже в той же функции мы копируем этот объем памяти в наш буфер:

 s2n(payload, bp); 
 memcpy(bp, pl, payload); 

Здесь s2n(c, l) перемещает размер полезной нагрузки в bp (в противоположность тому, что n2s ), а memcpy копирует количество байтов полезной нагрузки pl в bp . Это тоже не кажется большой проблемой, пока вы не рассмотрите фактический размер pl . Что, если pl всего в несколько байт? Или даже всего один байт? Злоумышленник мог сказать нам, что полезная нагрузка составляет 64 КБ, тогда как это всего лишь 1 байт. Итак, откуда будет скопирована остальная часть памяти, если pl намного меньше, чем ожидалось? Окружающая память , которая могла быть недавно освобождена от того же процесса.

В окружающей памяти потенциально хранится много конфиденциальных данных, таких как имена пользователей, пароли и даже закрытые ключи. Это беспокоит многих, так как злоумышленник может постоянно отправлять вредоносные биения на сервер и каждый раз возвращать 64 КБ своей памяти. Затем выполните простой поиск по ключевым словам, таким как «пароль» или «кредитная карта», и вуаля, злоумышленник украл ваши данные, даже не подозревая об этом. Марк Ломан, исследователь безопасности, уже показал, что эта атака возможна с помощью Yahoo Mail.

К счастью для нас, закрытый ключ, скорее всего, безопасен, как отмечает Нил Мехта (один из исследователей, обнаруживших ошибку):

Шаблоны распределения кучи делают маловероятным раскрытие закрытого ключа для #heartbleed #dontpanic .

- Нил Мехта (@neelmehta) 8 апреля 2014 г.

Шон Кэссиди дает очень полезную информацию об этих методах распределения кучи:

Есть два способа динамического выделения памяти с помощью malloc (при
минимум в Linux): используя sbrk (2) и используя mmap (2). Если память
, выделенный с помощью sbrk, затем он использует старые правила увеличения кучи и
ограничивает то, что можно найти с этим, хотя несколько запросов
(особенно одновременно) все еще мог найти кое-что интересное1.

На самом деле распределения для bp вообще не имеют значения. Распределение
для pl, однако, имеет большое значение. Это почти наверняка выделено
с sbrk из-за порога mmap в malloc. Тем не мение,
интересных вещей (например, документы или информация о пользователе), скорее всего, будут
выделен с помощью mmap и может быть доступен с pl. Несколько
одновременных запросов также сделают доступными некоторые интересные данные.

Как это исправить

Поскольку эту ошибку очень просто выполнить, значит, ее также легко исправить. OpenSSL уже выпустил обновление 1.0.1g. Он содержит следующее исправление:

 /* Read type and payload length first */ 
 if (1 + 2 + 16 > s->s3->rrec.length) 
 return 0; /* silently discard */ 
 hbtype = *p++; 
 n2s(p, payload); 
 if (1 + 2 + payload + 16 > s->s3->rrec.length) 
 return 0; /* silently discard per RFC 6520 sec. 4 */ 
 pl = p; 

Как видите, разница заключается в проверке длины, которая, во-первых, гарантирует, что тактовый сигнал не пуст (длина 0), а во-вторых, проверяет, что полезная нагрузка тактового сигнала соответствует длине, предоставленной пользователем. Это так просто. Четыре строчки кода теперь защищают более 66% серверов от катастрофической ошибки Heartbleed.

Как это исправлено

У вас есть общедоступный сервер, использующий SSL? Тогда вам обязательно стоит его пропатчить. Я могу только предположить, что кто-то уже установил war-dialer, который проверяет каждый сервер на наличие этой ошибки, а затем использует те, у кого она есть. Итак, если вы используете Ubuntu, выполните следующее:

 sudo apt-get update 
 sudo apt-get install -y libssl1.0.0 openssl 
 
 # Verify that the Build Date is April 7th 2014 or later 
 openssl version -a 

Затем обязательно перезапустите все службы, использующие OpenSSL. Если вы не уверены, что патч сработал, просто используйте этот инструмент, чтобы узнать.

comments powered by Disqus

Содержание