Наследование DMARC поддоменами: тег sp и как закрыть дыру для спуфинга
p=reject на организационном домене не защищает поддомены так, как думает большинство админов. Разбираем точную механику наследования из RFC 7489, показываем, почему sp=none — это открытая дверь для фишинга с несуществующих поддоменов, и даём готовые DNS-записи для продакшна, parked-доменов и рассылок через ESP.
EvilMail Team19 июля 2026 г.11 мин чтения
Классический фишинг сегодня редко идёт с вашего основного домена — он под p=reject, почтовики его отбивают, атакующий это знает. Он бьёт туда, где у вас формально ничего нет: billing.company.com, secure-login.company.com, pay.company.com. Этих поддоменов нет в DNS, MX у них нет, но в поле From они выглядят как ваш бренд. Пройдёт такое письмо или нет — решает один тег в вашей DMARC-записи, который большинство админов не заполняют вообще: sp.
Если у вас p=reject, но sp не продуман, защиты от спуфинга поддоменов у вас нет. Есть иллюзия защиты. Разберём, почему так, на уровне механики резолвера, а не общих слов.
Как резолвер на самом деле ищет DMARC-запись для поддомена
Главное заблуждение: DMARC якобы «спускается» по дереву поддоменов, и политика родителя автоматически покрывает всё ниже. Это не так. RFC 7489 §6.6.3 описывает ровно два шага поиска, и промежуточные уровни в нём не участвуют.
Возьмём входящее письмо с From-доменом a.mktg.company.com. Принимающий сервер делает следующее:
Запрашивает TXT-запись строго по имени _dmarc.a.mktg.company.com.
Если её нет (NXDOMAIN или нет TXT с v=DMARC1) — вычисляет организационный домен и запрашивает _dmarc.company.com.
Всё. Уровень _dmarc.mktg.company.comне опрашивается никогда, если только From не был ровно mktg.company.com. Между полным именем поддомена и организационным доменом — пропасть, и промежуточных запросов в неё не проваливается.
Ключевой момент этой диаграммы — что такое «организационный домен». Это не «домен минус один уровень» и не «две метки снизу». Организационный домен определяется по Public Suffix List (publicsuffix.org). Для a.mktg.company.co.uk организационный домен — company.co.uk, потому что co.uk — публичный суффикс. Если наивно резать по двум меткам, получите co.uk и сломаете всю логику. Почтовые библиотеки это учитывают, ваши самописные проверки — часто нет.
Что делает тег sp и чем он отличается от p
Политикой управляют два тега:
p — политика для самого организационного домена (company.com).
sp — политика для поддоменов, у которых нет собственной _dmarc-записи.
И три состояния, которые надо держать в голове:
`sp` отсутствует — поддомены наследуют значение p. То есть p=reject без sp действительно закрывает поддомены. Безопасный дефолт, но неявный.
`sp=none` — самая частая и самая опасная ошибка. Вы явно сказали: «для поддоменов политики нет». Все поддомены, включая несуществующие, становятся полностью спуфируемыми, даже при p=reject.
`sp=reject` — явно и жёстко закрывает все поддомены без своей записи.
Отдельно: если у поддомена есть собственная `_dmarc`-запись, теги p и sp родителя для него игнорируются целиком. _dmarc.mktg.company.com со значением v=DMARC1; p=none полностью перекрывает sp=reject на родителе. Это не баг — это механизм делегирования, которым мы дальше воспользуемся для ESP.
Тонкость с PSL, о которой почти никто не помнит: если ваш поддомен сам попал в Public Suffix List (так делают некоторые хостинг-платформы для изоляции клиентов), он становится «организационным» с точки зрения DMARC, и наследование от вашего company.com к нему рвётся. Редко, но если вы на такой платформе — проверяйте.
Атака через несуществующий поддомен
Разберём конкретный вектор — абстракция здесь только мешает. У вас company.com с записью:
Поддомена billing.company.com не существует. Нет ни A, ни MX, ни SPF, ни DKIM. Атакующему это и не нужно — ему нужно только ваше имя в поле From. Ключевой факт: DMARC проверяет выравнивание с доменом из заголовка From (RFC 5322.From), а не с MAIL FROM/Return-Path. From здесь — billing.company.com.
Принимающий сервер идёт по §6.6.3: _dmarc.billing.company.com → NXDOMAIN → организационный домен _dmarc.company.com → находит запись → From — поддомен, значит применяется sp → sp=none. В заголовках получателя вы увидите примерно это:
dmarc=none — почтовик не отклоняет письмо, потому что вы сами сказали, что политики для поддоменов нет. Замените sp=none на sp=reject, и та же строка станет dmarc=fail (... dis=REJECT), а SMTP-транзакция закончится 550 5.7.1. Вся разница между «фишинг доставлен» и «фишинг отбит на уровне SMTP» — в одном слове.
Неиспользуемые (parked) домены: reject по умолчанию
Домены без почты — редиректы, брендозащита, купленные «на всякий случай», старые проекты — любимая мишень, потому что про них все забыли. На evilmail.pro мы регулярно подчищаем именно эти хвосты у клиентов: основной домен вылизан, а десяток parked-доменов открыт нараспашку.
Для домена, который не должен отправлять почту вообще, ставьте глухую конфигурацию из четырёх записей:
; DMARC — отклонять и домен, и все поддомены
_dmarc.company.com. IN TXT "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s;"
; SPF — никто не имеет права слать
company.com. IN TXT "v=spf1 -all"
; DKIM — отозванный ключ (wildcard-селектор)
*._domainkey.company.com. IN TXT "v=DKIM1; p="
; Null MX (RFC 7505) — домен явно не принимает почту
company.com. IN MX 0 .
adkim=s; aspf=s (strict-выравнивание) здесь уместны именно потому, что легитимной почты нет — ужесточение ничего не ломает. Пустой p= в DKIM — это «ключ отозван», а не «ключа нет». Null MX (MX 0 .) говорит отправителям, что домен не принимает почту, и хорошие MTA сразу отбивают bounce-ы, сокращая поверхность для backscatter.
Продакшн-домен с легитимными рассылками на поддоменах
Реальный кейс: company.com под p=reject, а mktg.company.com шлёт маркетинг через ESP (SendGrid, Mailgun, Amazon SES). Здесь sp=reject на родителе — правильная база, но ESP на старте почти наверняка не пройдёт strict-выравнивание. Два рабочих подхода.
Подход 1 — делегирование через отдельную запись. Держите sp=reject на родителе, но заведите отдельную _dmarc для поддомена, которая перекроет наследование на время onboarding:
_dmarc.company.com. IN TXT "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s; rua=mailto:[email protected]"
_dmarc.mktg.company.com. IN TXT "v=DMARC1; p=quarantine; adkim=r; aspf=r; rua=mailto:[email protected]"
Пока настраиваете SPF/DKIM у ESP, mktg живёт под quarantine с relaxed-выравниванием и не блокирует рассылку. Когда отчёты показывают стабильный pass — поднимаете mktg до p=reject или вовсе убираете его запись, чтобы он снова наследовал sp=reject.
Подход 2 — сразу под sp=reject. Если ESP уже настроен правильно (подписывает DKIM вашим доменом и SPF выровнен), отдельная запись не нужна — mktg наследует sp=reject и проходит за счёт выравнивания.
Про adkim/aspf в контексте поддоменов: значение по умолчанию r (relaxed) считает выравнивание успешным, если совпадают организационные домены. То есть DKIM-подпись от mktg.company.com при relaxed выровнена с From company.com. При s (strict) домены должны совпадать точно, до метки поддомена. Strict жёстче и правильнее для безопасности, но ломает большинство ESP, которые подписывают собственным поддоменом. Не включайте s на домене с активными рассылками, пока не убедитесь по RUA, что подписи выровнены точно.
Развёртывание без блокировки почты: p=none → мониторинг → sp
Никогда не включайте sp=reject вслепую на живом домене. Порядок такой:
1.Начните с p=none; sp=none; rua=mailto:[email protected]; fo=1. Ничего не блокируется, но вы начинаете получать агрегированные XML-отчёты (по умолчанию раз в сутки от каждого получателя).
2.Собирайте RUA 2–4 недели. В отчётах найдите все отправляющие поддомены — включая забытые CRM, биллинг, мониторинг, старые формы.
3.Поднимите сначала sp, потом p. Так вы сначала закрываете поддомены (где легитимной почты обычно меньше), а основной домен трогаете последним.
<header_from> показывает, от какого поддомена шло письмо, <policy_evaluated><disposition> — что применилось. Строки с disposition=none и dkim/spf=fail от незнакомого IP на несуществующем поддомене — это либо забытый легитимный сервис, либо активный спуфинг. Разберите каждую прежде, чем поднимать sp.
fo=1 заставляет генерировать forensic-отчёт при провале любой из проверок SPF или DKIM (дефолт fo=0 — только когда провалились обе). Для расследований fo=1 информативнее; добавьте ruf=mailto:[email protected], если хотите получать сами образцы (учтите приватность — многие получатели forensic не шлют вовсе).
Ещё одна ловушка: pct управляет применением основной политики и на sp в спецификации предсказуемо не распространяется, а разные получатели трактуют его по-своему. Не пытайтесь выкатывать поддомены частично через pct — на sp полагаться на это нельзя.
Чек-лист перед включением sp=reject
Собрали RUA минимум 2 недели и выписали все отправляющие поддомены из <header_from>.
Для каждого активного поддомена проверили SPF- и DKIM-выравнивание — в отчётах стабильный pass, не fail.
Для исключений (ESP, транзакционка) завели отдельные _dmarc.<sub> записи с нужной политикой.
На всех parked-поддоменах и parked-доменах — Null MX (MX 0 .) и v=spf1 -all.
Проверили DNS на wildcard-записи (*.company.com), которые могут «оживить» несуществующие поддомены и сбить логику.
Протестировали отклонение вручную:
bash
dig +short TXT _dmarc.company.com
dig +short TXT _dmarc.mktg.company.com # есть ли у поддомена своя запись?
# отправить «спуф» на ящик у DMARC-строгого провайдера и проверить отбой
swaks --to [email protected] --from [email protected] \
--server gmail-smtp-in.l.google.com
После включения sp=reject не выключаете RUA — держите мониторинг ещё месяц, чтобы поймать сломавшийся легитимный сервис.
Если нужен forensic — добавьте ruf и fo=1, но заранее продумайте приватность образцов.
p=reject без продуманного sp — это замок на входной двери при распахнутых окнах. Дыра в наследовании поддоменов не теоретическая: она эксплуатируется каждый день, потому что не требует от атакующего ни вашего MX, ни ваших ключей — только вашего имени в поле From. Один тег, sp=reject, закрывает её для всего, у чего нет собственной причины быть открытым.