Уязвимость BIND позволяет «уронить» любой сервер: как и почему это работает
Почти месяц назад, 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. Это необходимо для определения того, содержал ли пакет ответ на наш запрос: Таким образом, становится очевидно, что эксплуатация уязвимости происходит через неполное выполнение условия из патча. Попробуем понять, какое сочетание записей в пакете необходимо для эксплуатации.
Пишем эксплоиты
Попасть в ветку с уязвимым кодом можно через следующее краткое условие
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
.Всего 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 запроса.
Как и предполагалось, мы видим экстренное завершение работы Named демона:
Что в итоге
Рассмотренная уязвимость достаточно проста и опасна еще и тем, что для ее эксплуатации не требуется сложных условий. Разработчики BIND постоянно исправляют серьезные уязвимости DNS сервера, позволяющие нарушить его работу.
Эксперты Positive Technologies внимательно изучили все устраненные разработчиками уязвимости, воспроизвели их в реальных условиях и разработали IDS сигнатуры для обнаружения эксплуатации.
@ISCdotORG #BIND remote #DoS
— Attack Detection (@AttackDetection) February 7, 2017
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
Автор: Кирилл Шипулин, специалист группы исследования методов обнаружения атак
Positive Technologies