Уязвимость BIND позволяет «уронить» любой сервер: как и почему это работает

2d6892e9dead4810baf26637c691e794.jpg

Почти месяц назад, 11 января 2017 года, разработчики наиболее популярного DNS сервера с открытым исходным кодом BIND выпустили исправления для четырех новых уязвимостей, которые позволяют удаленному атакующему аварийно завершить работу DNS сервера. В числе уязвимостей присутствует CVE-2016–9147, о которой далее пойдет речь. Атака не требует специальных условий кроме необходимости злоумышленнику видеть исходящий от уязвимого сервера трафик.

Мы задались целью создать правила (NAD) для обнаружения эксплуатации данных уязвимостей по сети — чтобы это сделать, нам пришлось глубже разобраться с кодом BIND и написать собственные эксплоиты. Наш разбор поможет понять, как все устроено внутри столь популярного DNS-сервера, а также узнать о просчетах, допущенных разработчиками проекта, и возможных решениях этих проблем.

В чем проблема


Под наибольшим риском находятся рекурсивные серверы с поддержкой DNSSEC, поэтому далее мы рассмотрим именно случай рекурсивного запроса.

В описании патча утверждается, что определенное сочетание DNSSEC записей в ответе на рекурсивный запрос может вызвать отказ в обслуживании сервера или, по-простому, крэш. Кроме того, разработчики дополняют, что такое сочетание полей не встречается в нормальном DNS-трафике.

Для дальнейшего понимания проблемы необходимо изучить публично доступный патч для этой CVE. Патч исправляет всего несколько строк кода в одном из модулей, ответственных за обработку DNS ответов.

--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -6984,15 +6984,19 @@ answer_response(fetchctx_t *fctx) {
                                                 * a CNAME or DNAME).
                                                 */
                                                INSIST(!external);
-                                               if ((rdataset->type !=
-                                                    dns_rdatatype_cname) ||
-                                                   !found_dname ||
-                                                   (aflag ==
-                                                    DNS_RDATASETATTR_ANSWER))
+                                               /*
+                                                * Don't use found_cname here
+                                                * as we have just set it
+                                                * above.
+                                                */
+                                               if (cname == NULL &&
+                                                   !found_dname &&
+                                                   aflag ==
+                                                    DNS_RDATASETATTR_ANSWER)
                                                {
                                                        have_answer = ISC_TRUE;
-                                                       if (rdataset->type ==
-                                                           dns_rdatatype_cname)
+                                                       if (found_cname &&
+                                                           cname == NULL)
                                                                cname = name;
                                                        name->attributes |=
                                                            DNS_NAMEATTR_ANSWER;

Как мы можем видеть, патч исправляет логическую ошибку в условии
if ((rdataset->type != dns_rdatatype_cname) || !found_dname || (aflag == DNS_RDATASETATTR_ANSWER))
на 
if (cname == NULL && !found_dname && aflag == DNS_RDATASETATTR_ANSWER)

Выполнение всех трех выражений стало обязательным для изменения переменной have_answer в true. Это необходимо для определения того, содержал ли пакет ответ на наш запрос:

84b58f0c4160486ebd612d424be7feb3.png

Таким образом, становится очевидно, что эксплуатация уязвимости происходит через неполное выполнение условия из патча. Попробуем понять, какое сочетание записей в пакете необходимо для эксплуатации.

Пишем эксплоиты


Попасть в ветку с уязвимым кодом можно через следующее краткое условие
6968                                 if (found) {

Переменная found устанавливается в пяти блоках кода из этой же функции, которые обрабатывают различные вариации ответов, такие, как перенаправление имен записями CNAME или DNAME, ответ на запрос с типом ANY и т.д.:

6891                      if (rdataset->type == type && !found_cname) {
6892                      /*
6893                      * We've found an ordinary answer.
6894                      */


6899                      } else if (type == dns_rdatatype_any) {
6900                      /*
6901                      * We've found an answer matching
6902                      * an ANY query.  There may be
6903                      * more.
6904                      */


6907                      } else if (rdataset->type == dns_rdatatype_rrsig
6908                            && rdataset->covers == type
6909                            && !found_cname) {
6910                            /*
6911                            * We've found a signature that
6912                            * covers the type we're looking for.
6913                            */


6917                      } else if (rdataset->type ==
6918                            dns_rdatatype_cname
6919                            && !found_type) {
6920                            /*
6921                            * We're looking for something else,
6922                            * but we found a CNAME.
6923                            *


6955                      } else if (rdataset->type == dns_rdatatype_rrsig
6956                            && rdataset->covers ==
6957                                dns_rdatatype_cname
6958                            && !found_type) {
6959                            /*
6960                            * We're looking for something else,
6961                            * but we found a SIG CNAME.
6962                            */


Вспомним небольшую подсказку в описании патча:

«Named mishandled some responses where covering RRSIG records are returned without the requested data resulting in a assertion failure. (CVE-2016–9147)»


Которая говорит, что одна из записей должна быть записью RRSIG. RRSIG — это один из механизмов DNSSEC, обеспечивающий целостность DNS данных в ответе. Для записей любого типа (A, AAAA, NS, DNAME, CNAME и т.д.) в ответе передается соответствующая им запись RRSIG, которая содержит цифровую подпись. Для того, чтобы понять, для какого типа RRSIG содержит подпись, в RRSIG существует поле Type Covered.

34431fb6d5ee4ea7a05169dcc7c7c7ae.png

Всего 2 из 5 условий связаны с обнаружением RRSIG в DNS ответе:

6907                                 } else if (rdataset->type == dns_rdatatype_rrsig
6908                                            && rdataset->covers == type
6909                                            && !found_cname) {
6910                                         /*
6911                                         * We've found a signature that
6912                                         * covers the type we're looking for.
6913                                         */
6914                                         found = ISC_TRUE;
6915                                         found_type = ISC_TRUE;
6916                                         aflag = DNS_RDATASETATTR_ANSWERSIG;

Данная проверка срабатывает, если мы обнаружили RRSIG с подписью для той записи, что мы ищем (для которой посылали запрос).
6955                                 } else if (rdataset->type == dns_rdatatype_rrsig
6956                                            && rdataset->covers ==
6957                                               dns_rdatatype_cname
6958                                            && !found_type) {
6959                                         /*
6960                                         * We're looking for something else,
6961                                         * but we found a SIG CNAME.
6962                                         */
6963                                         found = ISC_TRUE;
6964                                         found_cname = ISC_TRUE;
6965                                         aflag = DNS_RDATASETATTR_ANSWERSIG;
6966                                 }

А это условие подобно первому, только запись RRSIG должна покрыть тип CNAME.

Мы вплотную подошли к заключению, что для эксплуатации этой ошибки требуется одна единственная запись RRSIG в ANSWER_SECTION. И действительно, такая ситуация не должна встречаться в обычном DNS трафике, т.к. RRSIG не может быть не привязана к другой записи.

Пробуем воспроизвести топологию с рекурсивным DNS сервером и на рекурсивный запрос послать ответ из одной RRSIG записи, которая покрывает или CNAME или тип из DNS запроса.

6b8ac85f5cdb4fabbfa26c6665488bfc.png

Как и предполагалось, мы видим экстренное завершение работы Named демона:

3b4a60046ed64602ba7085d71a279d75.png

Что в итоге


Рассмотренная уязвимость достаточно проста и опасна еще и тем, что для ее эксплуатации не требуется сложных условий. Разработчики BIND постоянно исправляют серьезные уязвимости DNS сервера, позволяющие нарушить его работу.

Эксперты Positive Technologies внимательно изучили все устраненные разработчиками уязвимости, воспроизвели их в реальных условиях и разработали IDS сигнатуры для обнаружения эксплуатации.

@ISCdotORG #BIND remote #DoS
CVE-2016–9147
Affected: 9.9.9-P4, 9.9.9-S6, 9.10.4-P4, and 9.11.0-P1#Suricata rule: https://t.co/D4RujnkE9k

— Attack Detection (@AttackDetection) February 7, 2017

Автор: Кирилл Шипулин, специалист группы исследования методов обнаружения атак
Positive Technologies

Комментарии (0)

© Habrahabr.ru