DNS Rebinding: как браузер превращается в инструмент атаки на вашу внутреннюю сеть.
Представьте: сотрудник открывает ссылку в браузере — на первый взгляд обычный сайт. Никаких предупреждений, никаких вирусов. Но в этот момент его браузер уже сканирует внутреннюю сеть офиса и отправляет данные атакующему. Роутер, внутренние сервисы, панели администрирования — всё, до чего браузер может дотянуться.
Это DNS rebinding. Атака известна с 1996 года, она до сих пор работает, и большинство администраторов о ней не думают.
КАК БРАУЗЕР ЗАЩИЩАЕТ ВНУТРЕННИЕ РЕСУРСЫ — И ПОЧЕМУ ЭТО ЛОМАЕТСЯ
Браузер живёт по правилу same-origin policy: JavaScript со страницы evil.com не может читать ответы от bank.com. Это базовая защита от межсайтовых атак.
Но у этого правила есть слепое пятно. Политика same-origin опирается на имя домена, а не на IP-адрес. Браузер не отслеживает что за IP-адрес стоит за доменом — главное, что запрос идёт к тому же имени хоста.
DNS rebinding использует именно это. Атакующий контролирует домен evil.com и его DNS-сервер. Всё начинается с того, что жертва открывает страницу на evil.com.
АНАТОМИЯ АТАКИ ШАГ ЗА ШАГОМ
Шаг 1. Жертва открывает http://evil.com. Браузер делает DNS-запрос — получает настоящий IP атакующего, например 1.2.3.4. Страница загружается. На ней есть JavaScript.
Шаг 2. DNS-запись для evil.com выставлена с TTL = 1 секунда. TTL (Time To Live) — это время, на которое браузер кэширует DNS-ответ. Обычно это минуты или часы. Здесь — одна секунда. Браузер почти сразу забудет старый IP и при следующем запросе пойдёт к DNS снова.
Шаг 3. JavaScript на странице ждёт несколько секунд, потом делает новый запрос к evil.com — например, fetch('http://evil.com/api/data').
Шаг 4. Браузер снова делает DNS-запрос для evil.com. Но теперь DNS-сервер атакующего отвечает другим IP — например, 192.168.1.1. Это IP роутера жертвы в локальной сети.
Шаг 5. Браузер считает: домен тот же — evil.com, same-origin не нарушается. Запрос уходит на 192.168.1.1 — прямо на роутер жертвы. JavaScript читает ответ и отправляет его атакующему.
Жертва об этом не знает ничего.
ЧТО АТАКУЮЩИЙ МОЖЕТ СДЕЛАТЬ
После успешного rebinding браузер жертвы — это прокси внутрь её сети. Атакующий через JavaScript может:
Сканировать внутреннюю сеть — перебирать адреса 192.168.1.1 — 192.168.1.254, проверять открытые порты, определять какие сервисы запущены.
Атаковать роутер — большинство домашних и офисных роутеров имеют веб-интерфейс на порту 80 или 8080. Без пароля или с заводским — полный доступ.
Атаковать внутренние сервисы — панели управления серверами, Grafana, Jenkins, Kubernetes dashboard, Elasticsearch без аутентификации, внутренние API. Всё что доступно с машины жертвы — доступно атакующему.
Атаковать localhost — если на машине жертвы крутится что-то на 127.0.0.1 (dev-сервер, локальная БД, REST API), rebinding работает и туда. Атакующий меняет IP не на 192.168.x.x, а на 127.0.0.1.
РЕАЛЬНЫЕ СЛУЧАИ
В 2018 году исследователь Brannon Dorsey показал DNS rebinding против Google Home и Chromecast — устройства сливали геолокацию и позволяли управлять собой через браузер любого, кто открыл вредоносную страницу. Параллельно Tavis Ormandy из Google Project Zero демонстрировал ту же атаку против BitTorrent-клиентов uTorrent и Transmission, а также игр Blizzard. Google закрыл уязвимость через несколько месяцев после публикации.
В том же 2018 году NCC Group выпустили Singularity — открытый фреймворк для автоматизации DNS rebinding атак. Демо доступно на rebind.it. Атака перестала требовать специальных знаний.
Уязвимы все браузеры — или почти все. В октябре 2025 года Chrome 142 добавил Local Network Access (LNA) — теперь сайты должны запросить у пользователя разрешение перед любым запросом во внутреннюю сеть или на loopback. Молчаливый rebinding без взаимодействия с пользователем в Chrome 142+ больше не работает. Firefox внедряет LNA постепенно — по умолчанию включён в Nightly, с Firefox 147 разворачивается для пользователей с Enhanced Tracking Protection Strict. Safari LNA не реализовал.
КАК ЗАЩИТИТЬСЯ
Chrome 142 LNA частично закрывает проблему на стороне браузера, но полагаться только на него нельзя: Firefox и Safari отстают, пользователь может нажать «Разрешить» на prompt, а сервер не знает с какого браузера к нему придёт запрос. Короткий TTL — легитимная настройка DNS, запретить её нельзя. Надёжная защита строится на инфраструктуре независимо от браузера.
Уровень 1 — DNS resolver с защитой от rebinding
Этот уровень защищает клиентские машины и офисные сети — там где браузер реально используется и может стать прокси для атакующего. На сервере браузера нет, поэтому серверу этот уровень не нужен. Но для вашей рабочей машины или корпоративной сети — важен.
Для самого сервера — хорошей практикой будет настроить шифрованный DNS через systemd-resolved. Это не защищает от rebinding, но шифрует DNS-трафик сервера — провайдер не видит какие домены резолвит машина, и защищает от подмены DNS-ответов посередине:
[Resolve]
DNS=9.9.9.9#dns.quad9.net
DNSOverTLS=yes
Здесь 9.9.9.9 — IP-адрес Quad9, #dns.quad9.net — имя хоста для проверки TLS-сертификата. DNSOverTLS=yes включает шифрование DNS-запросов. После изменения:
sudo systemctl restart systemd-resolved
Против rebinding конкретно помогает dnsmasq с опцией stop-dns-rebind. Это настраивается на роутере (если поддерживает dnsmasq, например OpenWRT) — и защищает все устройства в сети сразу:
# /etc/dnsmasq.conf
stop-dns-rebind
rebind-localhost-ok
stop-dns-rebind заставляет dnsmasq отбрасывать ответы, где публичный домен резолвится в RFC1918-адрес (192.168.x.x, 10.x.x.x, 172.16-31.x.x) или 127.x.x.x. rebind-localhost-ok — исключение для 127.0.0.1: некоторые легитимные сервисы на роутере используют loopback для внутренних нужд, без этой строки они сломаются.
Уровень 2 — проверка Host-заголовка в nginx
Это ключевая защита для любого сервиса. Если nginx принимает запросы только от известных имён хостов — rebinding не сработает, потому что запрос придёт с заголовком Host: evil.com, а не с ожидаемым именем.
server {
listen 80 default_server;
# Блок по умолчанию — сбрасывает всё неизвестное
return 444;
}
server {
listen 80;
server_name myserver.example.com;
# Только этот хост обрабатывается
}
return 444 — nginx-специфичный код: сервер закрывает соединение без единого байта ответа. Атакующий не получает даже статус ошибки — соединение просто обрывается.
Для внутренних сервисов где нужно принимать запросы и по имени хоста и по IP — можно явно перечислить разрешённые варианты:
server {
listen 8080;
server_name internal.company.local 192.168.1.50;
if ($host !~* ^(internal\.company\.local|192\.168\.1\.50)$) {
return 444;
}
}
$host — переменная nginx с значением Host-заголовка входящего запроса. Условие !~* означает «не совпадает без учёта регистра». Если Host не равен ни internal.company.local ни 192.168.1.50 — соединение закрывается. Любой rebinding с чужим доменом получит 444.
Уровень 3 — аутентификация на всех внутренних сервисах
Rebinding работает только если сервис отвечает на запросы без проверки. Grafana без пароля, Jenkins без авторизации, Elasticsearch на 0.0.0.0 — всё это мишени.
Правило простое: любой сервис с веб-интерфейсом должен требовать аутентификацию, даже если он «внутренний» и «недоступен снаружи».
Уровень 4 — сетевая сегментация
Сервисы которые не должны быть доступны из браузера — не должны быть доступны с рабочих машин. VLAN, nftables, firewall-правила на уровне сети.
nft add rule inet filter forward \
ip saddr 192.168.10.0/24 \
ip daddr 192.168.20.0/24 \
tcp dport { 8080, 9090, 5601 } drop
Правило читается так: любой трафик с рабочих станций (192.168.10.0/24) к серверам (192.168.20.0/24) на порты 8080 (панели управления), 9090 (Prometheus) и 5601 (Kibana) — отбросить. Даже если браузер рабочей станции будет использован как прокси через rebinding — он физически не достучится до этих сервисов. IP-диапазоны замените на свои.
ПРОВЕРЬТЕ СВОЮ ИНФРАСТРУКТУРУ
Шаг 1. Посмотрите что слушает на всех интерфейсах — это сервисы доступные из сети, а не только с localhost:
ss -tlnp | grep -v 127.0.0.1
Каждая строка в выводе — это порт открытый для сетевых подключений. Проверьте вручную что каждый из них требует аутентификацию:
curl http://localhost:3000 # Grafana — должен просить логин
curl http://localhost:9200 # Elasticsearch — не должен отвечать без auth
curl http://localhost:8080 # Jenkins и другие панели
Шаг 2. Проверьте nginx на наличие блока который принимает запросы с любым Host-заголовком:
grep -r "default_server\|server_name _" /etc/nginx/
Команда ищет два паттерна. default_server — блок который nginx использует когда ни один server_name не совпал с Host-заголовком запроса. server_name _ — явный catch-all, принимающий любое имя хоста.
Если команда что-то нашла — это ещё не проблема. Важно что файл делает с запросом. Проверьте активен ли он:
ls /etc/nginx/sites-enabled/
Если найденный файл есть в sites-enabled — откройте его и посмотрите на блок location /. Опасно: там стоит try_files или proxy_pass — сервер отдаёт контент на любой Host. Безопасно: там стоит return 444 — сервер закрывает соединение без ответа.
Если явного default_server нет совсем — nginx отдаёт запросы с неизвестным Host в первый попавшийся активный блок. Это тоже нужно закрыть. Создайте новый файл и вставьте в него следующий конфиг:
sudo nano /etc/nginx/sites-available/default-catch-all.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
Сохраните файл (Ctrl+O, затем Ctrl+X в nano), активируйте его симлинком и перезагрузите nginx:
sudo ln -s /etc/nginx/sites-available/default-catch-all.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Проверьте что защита работает — запрос с чужим Host должен вернуть пустой ответ:
curl -v -H "Host: evil.com" http://localhost/
# Ожидаемый результат: Empty reply from server
Онлайн-инструмент для проверки браузера: rebind.it — демо-сайт Singularity, показывает уязвим ли ваш браузер к rebinding прямо сейчас.
ИТОГ
DNS rebinding — атака которая известна с 1996 года и никуда не делась, потому что в её основе архитектурное ограничение, а не баг. Браузер нельзя обвинять — он работает по спецификации.
Защита не сложная: dnsmasq с stop-dns-rebind на роутере, проверка Host-заголовка в nginx, обязательная аутентификация на всех внутренних сервисах. Три шага, каждый из которых занимает минуты.
Открытый Grafana без пароля «потому что он внутренний» — это не аргумент. Браузер вашего коллеги уже может быть прокси для атакующего.
