Маркетинг попросил вернуть старую промо-страницу - ту, что делали к акции три года назад. Открываю страницу по её адресу, чтобы посмотреть, что от неё осталось, и вижу чужой сайт. Домен наш, имя в адресной строке наше, замочек на месте, содержимое - не имеет к нам никакого отношения.
Страницу тогда выключили, а запись в DNS осталась. За три года она пережила и акцию, и сам сервис, на который указывала, - и в какой-то момент начала отправлять посетителей на чужой сайт. Такие записи называют висячими, и в зоне любой компании, которая живёт дольше пары лет, их обычно несколько десятков.
Как получается висячая запись
Разберём тот же случай по шагам. Маркетингу нужна промо-страница, и её кладут не к себе, а на внешний хостинг: статические страницы в объектном хранилище, GitHub Pages, конструктор сайтов. У всех у них одна модель - вы придумываете имя проекта, и хостинг отдаёт вам адрес вида имя-проекта.хостинг.com. Имя выдаётся по принципу «кто первый занял», как никнейм.
Чтобы страница открывалась на своём домене, в зоне компании заводят запись, которая перенаправляет на этот адрес:
promo CNAME company-promo-2023.<хостинг>.
Акция кончается. Проект на хостинге удаляют - счёт закрыт, задача закрыта. В зону никто не заглядывает, потому что DNS ведёт другая команда, а тикета на удаление никто не заводил.
Имя проекта company-promo-2023 на стороне хостинга освободилось. Оно снова доступно для регистрации - любому. А запись в вашей зоне продолжает всех туда отправлять.
Тот, кто зарегистрирует это имя, начнёт отдавать собственный контент с адреса promo.example.com. И это не домен, похожий на ваш, а ваш собственный: сертификат выпишется автоматически, в адресной строке будет ваше имя, замочек будет на месте.
Почему это не ловится обычным мониторингом
Мониторинг доступности проверяет сайты, которые вы знаете. Висячая запись - по определению та, про которую забыли, её нет ни в одном списке.
Проверка «а резолвится ли имя» тоже не помогает - и вот здесь главная ловушка. Возьмём заведомо несуществующий проект на GitHub Pages и запросим у DNS его адреса:
$ dig +short nonexistent-abc123.github.io 185.199.109.153 185.199.111.153 185.199.110.153 185.199.108.153
Четыре адреса, всё в порядке. Только проекта с таким именем не существует - я его выдумал.
Так происходит потому, что у хостинга в DNS стоит запись-джокер на *.github.io: она отвечает на любое имя в этой зоне, независимо от того, зарегистрировано оно или нет. DNS отвечает за имена, а не за содержимое, и сведений о том, занят ли проект, в нём нет.
Значит, проверка на уровне DNS даёт одинаковое «да» и для рабочего сервиса, и для пустого места. Разница появляется, только когда вы запросите по этому адресу саму страницу:
$ curl -s -o /dev/null -w "%{http_code}\n" http://nonexistent-abc123.github.io/ 404 $ curl -s http://nonexistent-abc123.github.io/ | grep -o '<title>.*</title>' <title>Site not found · GitHub Pages</title>
То же самое у объектного хранилища, только ответ приходит в XML:
$ curl -s http://nosuchbucket-abc123.s3.amazonaws.com/ <?xml version="1.0" encoding="UTF-8"?> <Error><Code>NoSuchBucket</Code><Message>The specified bucket does not exist</Message> <BucketName>nosuchbucket-abc123</BucketName>...
Site not found и NoSuchBucket - это и есть признак свободного имени: хостинг прямо говорит, что проекта у него нет, а ваша запись в DNS продолжает на него указывать. У большинства крупных сервисов такая страница своя и узнаваемая; если её нет, придётся сравнивать ответ с ответом на заведомо выдуманное имя того же хостинга.
Оговорка: часть хостингов уже требует подтвердить владение доменом, прежде чем к проекту можно привязать свой поддомен, - там сценарий закрыт. Проблема остаётся у тех, где такой проверки нет, и у записей, заведённых до того, как её ввели.
Чем это оборачивается
Возражение здесь понятное: промо-страница трёхлетней давности никому не нужна, кому она навредит. Но ущерб не в странице, а в имени, под которым она теперь отдаётся.
Cookie домена. Если приложение выставляет cookie на .example.com - а так делают, чтобы одна авторизация работала на всех поддоменах, - то страница на promo.example.com эти cookie получит. Браузер отработает штатно: область действия задали вы сами, и он приложит cookie к запросу на любой поддомен внутри неё. Дальше их прочитает тот, кто этим поддоменом управляет. HttpOnly тут не помогает: читать cookie из JavaScript и не требуется, достаточно того, что браузер отправил их на сервер. Secure к этой ситуации отношения не имеет вовсе - он ограничивает только передачу по HTTPS. Помогает префикс __Host-: он привязывает cookie к конкретному хосту. Цена - та самая единая авторизация на всех поддоменах, ради которой cookie и ставили на весь домен; выбирать приходится между удобством и этим риском.
Доверие к домену. Ссылка на promo.example.com в письме проходит и почтовый шлюз, и глаза самого получателя: домен-то родной. Все советы вида «смотрите на адрес» здесь работают против защищающегося.
Списки разрешённых источников. Ваш собственный поддомен часто оказывается в Content-Security-Policy, в Access-Control-Allow-Origin, в правилах прокси, в списке разрешённых redirect_uri у OAuth-клиента. Все эти списки писались, когда поддомен был ваш.
Подтверждение владения. Часть сервисов принимает подтверждение по файлу на конкретном хосте - и если этот хост попал в чужие руки, подтверждение получит тот, кто им управляет.
Откуда берётся зона, которую никто не помнит
Причины повторяются от компании к компании, и ни одна не про халатность конкретного человека.
DNS-запись живёт дольше проекта, потому что её удаление никогда не входит в чек-лист закрытия. Открытие - входит: без записи ничего не заработает, про неё вспомнят обязательно.
Записи заводят несколько команд, а зона одна. Инфраструктура, разработка, маркетинг, внешнее агентство - у каждого свои поддомены, общего реестра нет ни у кого.
Автоматика создаёт записи под каждую ветку и окружение. Часть контуров при этом выведена из-под автоматики «потому что там что-то важное», и записи от них удаляют руками - то есть не удаляют.
Компанию купили вместе с её зоной. Что там было - не знает уже никто.
Как это чинится
Взять зону целиком. Не по памяти, а выгрузкой у DNS-провайдера через его API или панель. Если зон несколько - все, включая те, что на почтовом провайдере и у регистратора отдельно.
Разметить владельцев. Каждая запись получает две пометки: команду-владельца и причину существования. Форма годится любая - комментарий в описании записи у провайдера, отдельная таблица, поле в системе, откуда зона разворачивается кодом; важно, чтобы пометка жила рядом с записью и обновлялась вместе с ней. Всё, для чего владельца не нашлось, идёт в отдельный список - с ним и работаем.
Проверить, куда указывает каждая запись. Для CNAME - существует ли на той стороне проект с таким именем: смотреть надо на ответ приложения, а не на факт резолва. Для A и AAAA - принадлежит ли ещё адрес вам; облачные адреса возвращаются в общий пул и достаются следующему арендатору, и это обычная штатная механика, а не сбой.
Удалять, а не «оставить на всякий случай». С формулировки «вдруг понадобится» вся история и начинается заново. Понадобится - заведут заново, это одна запись.
Закрыть источник. Удаление записи вносится в процедуру вывода сервиса из эксплуатации, рядом с отзывом сертификата и закрытием доступов. Без этого через год список соберётся снова, и вы будете разбирать его с нуля.
Что посмотреть у себя
Выгрузить зону и посчитать записи. В моей практике расхождение с тем, что называют по памяти, получается в разы - этого одного обычно достаточно, чтобы разговор про инвентаризацию перестал быть теоретическим.
Отобрать все CNAME, ведущие на внешние домены. Это группа с наибольшим риском: имя проекта на той стороне может освободиться и достаться кому-то другому. Для каждого проверить ответ сервиса, а не факт резолва.
Посмотреть, на какой домен приложение выставляет cookie. Если на .example.com, а не на конкретный хост - цена висячей записи вырастает, и разбор списка становится срочным.
И отдельно - посмотреть логи Certificate Transparency по своему домену: проще всего через поиск на crt.sh по маске %.example.com или через API любого монитора CT. Там перечислены все имена, для которых кто-либо когда-либо выписывал сертификат, - включая те, что выписывали не вы. Часть этих имён вы не найдёте ни в одном своём списке.

