C, PHP, VB, .NET

Дневникът на Филип Петров


* OpenSSL Heartbleed уязвимост при версии 1.0.1-1.0.1f

Публикувано на 11 април 2014 в раздел ОСУП.

В последните дни нашумя един от най-големите пробиви в сигурността в историята на Интернет. Състои се в потенциално разбиване на протокола TLS (SSL) <<< това е стара моя статия, в която не мисля, че съм го обяснил достатъчно коректно. По-рано тази година имаше подобен пробив при мобилни устройства на Apple (за тях ще пиша в следваща статия). Грешката при OpenSSL обаче е значително по-мащабна, защото по груби данни засяга над 20% от уебсайтовете, които използват TLS. В тази статия, подчертавайки че самия аз не съм задълбавал сериозно в темата, ще се опитам да обясня по максимално опростен начин какво точно представлява грешката. Визията ми е по-скоро да ви покажа какво не трябва вие самите да правите, когато програмирате.

Оригиналната услуга в TLS се нарича Heartbeat (сърцебиене). Услугата предлага протокол за осъществяване на „keep-alive“ функционалност – връзката между сървъра и клиента да бъде поддържана „жива“ и така да се пестят ресурси. Тя работи по следният начин:

  1. Клиентът изпраща кратко съобщение с произволно съдържание до сървъра;
  2. Сървърът връща същото съобщение обратно на клиента за потвърждение.

По-подробно всичко е описано в тази статия: https://tools.ietf.org/rfc/rfc6520.txt. За нас важен е следният фрагмент от сорс код (на C):

struct {
      HeartbeatMessageType type;
      uint16 payload_length;
      opaque payload[HeartbeatMessage.payload_length];
      opaque padding[padding_length];
} HeartbeatMessage;

По-нататък се обяснява, че общата дължина на съобщението НЕ трябва да надхвърля 2^14 или max_fragment_length според [RFC6066]. Променливата type може да е „heartbeat_request“ (заявка) или „heartbeat_response“ (отговор) – 1 байт. Променливата payload_length съдържа дължината на съобщението (отнема 2 байта), а payload съдържа самото съобщение (произволно съдържание). Padding е просто произволна поредица от символи, които получателят на съобщението пропуска – дължината му е (TLSPlaintext.length – payload_length – 3) и трябва да е поне 16 (или ексвивалента при DTLS). Ако payload_length на полученото съобщение е прекалено голям, то получения HeartbeatMessage трябва да бъде пропуснат без да бъде връщан отговор.

Това, което трябва да забележим е, че този който изпраща съобщението дефинира сам дължината му. Уязвимите функции са tls1_process_heartbeat() в ssl/t1_lib.c (или dtls1_process_heartbeat() за DTLS). Сега остава да видим какво прави бъгавия парсър (на OpenSSL):

...
unsigned int payload;
unsigned int padding = 16; /* Use minimum padding */

/* Read type and payload length first */
hbtype = *p++;
n2s(p, payload);
pl = p;
...

Тук „p“ е указател към съобщението. Типът на съобщението (HeartbeatMessageType) се записва в hbtype и указателя се премества напред. Прилага се n2s – макро, което копира два байта от „p“ в „payload“ – payload вече съдържа дължината на съобщението (payload_length). Указателят pl става указател към самото съобщение.

От тук насетне OpenSSL прочете точно толкова памет, колкото клиентът е указал чрез своя payload_length – числото е от 2 байта, т.е. OpenSSL ще прочете максимум 65535 байта за съобщението. Припомняме и 1 байт за типа и 16 байта за padding. Ето как:

buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;

/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);

След това чрез макрото s2n (обратното на n2s) то записва payload_length на request-а в response-а. Най-накрая се прави копиране с функцията memcpy от указателя в началото на съобщението на request-а до дължината на съобщението в response-а.

Видяхте ли вече къде е проблемът? Тук има класически buffer overrun! Този, който изпраща request изпраща дължината на съобщението и самото съобщение, но никъде не е гарантирано, че той не е казал по-голяма дължина и е изпратил по-късо съобщение. И хакерите съответно изпращат payload_length със стойност 65535, а предоставят payload от само 1 байт. Така дефакто OpenSSL им връща обратно не тяхното оригинално heartbeat съобщение, а и информацията, която се намира след него в рам паметта.

В общия случай не се знае какво точно ще бъде откраднато като информация, защото не се знае къде точно в паметта ще бъде записан буфера. Само, че heartbeat сигнали могат да се изпращат многократно, а следи не остават никакви. Тоест с малко упоритост хакера може да прочете почти цялата рам памет на компютъра на сървъра, който работи с уязвима версия на OpenSSL. А тази рам памет може да съдържа пароли на потребители, сесийни ключове, дори private key на SSL сертификата на сървъра… Което означава пълно компрометиране на връзката! Или вместо heartbeat (сърцебиене) се превръща в heartbleed (сърцекървене).

Този бъг го има във версии OpenSSL 1.0.1 до 1.0.1f. Бъгът е оправен в OpenSSL 1.0.1g чрез правене на проверка дали общите дължини на получения буфер и изготвения за връщане ще съвпаднат:

if (1 + 2 + payload + 16 > s->s3->rrec.length)
    return 0;

Или казано накратко – OpenSSL Heartbleed бъга е нещо, на което всеки един студент-първокурсник бива учен при уроците по програмиране: проверявайте границите на масивите (в случая буфера)! Второто нещо, което не е спазено, пък се казва на всеки курс по информационна сигурност: никога не вярвайте на подадената от потребителя информация! Естествено при много сложен софтуер тези неща се забелязват трудно…

Малко съвети за „юзърите“:

  • Ако ще влизате в SSL защитен уебсайт, в който има важна ваша информация, която искате да не бъде открадната, преди да се логвате в този сайт проверете дали не е уязвим на следния адрес: http://filippo.io/Heartbleed/
  • След като сте се уверили, че сайтът не е уязвим, можете да се логнете на него и е препоръчително да си смените паролата в този сайт – бъга в OpenSSL съществува от 2 години и не се знае кой-какво потенциално е откраднал! Направете го със сигурност ако сте се логвали в непроверен от вас сайт, в който сте влизали в последните 5 дни.

Съвети към администраторите:

  • Ако имате уязвима версия на OpenSSL, моментално я обновете;
  • Ако сте имали уязвима версия на OpenSSL, която сте обновили, задължително сменете своя SSL сертификат.

И не забравяйте – не са засегнати само уеб сървъри! Също толкова уязвими могат да са пощенските сървъри, FTP, и др., които използват OpenSSL. Например уязвими могат да бъдат дори домашните рутери (а как ще накараме юзърите да си сменят firmware… не е много ясно).

П.П. Как мислите – дали не трябва да се пусне мащабен пач на популярните браузъри да алармират автоматично при достъпване на heartbleed сървър?

Използвани източници:

1. http://blog.talosintelligence.com/2014/04/heartbleed-memory-disclosure-upgrade.html

2. http://www.theregister.co.uk/2014/04/10/many_clientside_vulns_in_heartbleed_says_sans/

 



2 коментара


  1. Questo каза:

    Чудесна и много информативна статия. Поздравления!

Добави коментар

Адресът на електронната поща няма да се публикува


*