Блог

DNS Rebinding: как браузер превращается в инструмент атаки на вашу внутреннюю сеть.

DNS-Rebinding
Linux

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.1192.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 без пароля «потому что он внутренний» — это не аргумент. Браузер вашего коллеги уже может быть прокси для атакующего.

Leave your thought here

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поддержать автора

Если вам понравилась статья — вы можете поддержать автора.

Crypto donation button by NOWPayments