Към съдържанието
PN Scripts

Сигурност

Лимитът от 10 заявки на SPF, DMARC и CAA: проверка на домейна ви отвън

  • PN Scripts
  • 9 мин четене

Един домейн обикновено се разваля тихо. Отделът по маркетинг добавя услуга за бюлетини, страницата ѝ за настройка казва „добавете нашия include към SPF записа си“, а седмица по-късно фактурите от счетоводната система започват да падат в спам. Или подновяването на сертификат се проваля през нощта, защото преди година някой е добавил CAA запис, в който е посочен само старият удостоверяващ орган. И двата проблема живеят в DNS, където ги четат само чужди сървъри. Тук показваме какво проверяват за домейна ви пощенските сървъри и удостоверяващите органи, как да преброите десетте заявки на SPF на ръка и с код, как да преминете с DMARC от наблюдение към защита и какво променя един CAA запис. Примерът на Python е пуснат на Python 3.12, а командите dig на Linux, точно както са отпечатани.

#Какво проверяват другите сървъри

Повечето неща се решават от четири малки отговора в DNS: MX записите казват кои сървъри приемат поща за домейна, SPF записът (TXT запис, който започва с v=spf1) казва кои сървъри могат да изпращат поща от името на домейна, DMARC записът в _dmarc казва какво да правят получателите с пощата, която не минава проверката, а CAA записът казва кои удостоверяващи органи могат да издават сертификати. И четирите се четат с dig:

dig +short MX example.com
dig +short TXT example.com
dig +short TXT _dmarc.example.com
dig +short CAA example.com

За example.com в деня, в който ги пуснахме, отговорите бяха 0 ., "v=spf1 -all", "v=DMARC1;p=reject;sp=reject;adkim=s;aspf=s" и нищо за CAA. Това е образец за домейн, който изобщо не изпраща и не получава поща: нулев MX (RFC 7505) казва на подателите да не опитват, SPF запис -all казва, че никой сървър не може да изпраща от името на домейна, а DMARC политика reject казва на получателите да отказват всичко, което твърди обратното. Паркираните домейни и домейните само за сайт заслужават същите три записа, защото спамърите обичат домейн, който няма нито един.

#SPF: един запис, ясен край, десет заявки

В RFC 7208 има три правила, които хващат повечето истински записи.

  • Точно един запис. Два TXT записа, които започват с v=spf1, водят до постоянна грешка при проверката. Това се случва, когато инструкциите на втора услуга се изпълнят буквално. Слейте ги в един.
  • Ясен край. ~all (мек отказ) или -all (отказ) казват на получателите какво да мислят за сървър, който не сте изброили. ?all не казва нищо, запис изобщо без all означава същото, а +all позволява на всеки сървър по света.
  • Най-много десет DNS заявки. Механизмите include, a, mx, ptr и exists и модификаторът redirect струват по една заявка, а include записите на вашите include записи също се броят. ip4, ip6 и all са безплатни. Над десет резултатът е постоянна грешка, а DMARC я приема за провал. RFC иска от получателите да допускат и не повече от две заявки, които не връщат нищо, и казва, че ptr не бива да се ползва.

Лимитът от десет заявки е този, който хваща хората неподготвени, защото не се вижда в самия запис. Запис с три елемента може да има нужда от дванадесет заявки. Този пример ги брои така, както ги брои RFC, като следва всеки include и спира при истински цикъл:

import re

# The terms that cost a DNS lookup (RFC 7208, section 4.6.4). The limit is 10.
COSTLY = {"include", "a", "mx", "ptr", "exists", "redirect"}


def spf_record(txt_records):
    found = [t for t in txt_records if t.lower() == "v=spf1" or t.lower().startswith("v=spf1 ")]
    if len(found) != 1:
        raise ValueError(f"{len(found)} SPF records (exactly one is allowed)")
    return found[0]


def count_lookups(domain, txt, path=()):
    """Worst case: every term is evaluated, as for a sender that matches nothing."""
    if domain in path:
        raise ValueError("loop: " + " > ".join(path + (domain,)))
    terms = []
    for term in spf_record(txt(domain)).split()[1:]:
        name, target = (re.split(r"[:=/]", term.lstrip("+-~?"), maxsplit=1) + [""])[:2]
        terms.append((name.lower(), target))
    has_all = any(name == "all" for name, _ in terms)
    total = 0
    for name, target in terms:
        if name == "redirect" and has_all:
            continue  # redirect= is ignored when the record has an all
        if name in COSTLY:
            total += 1
        if name in ("include", "redirect"):
            total += count_lookups(target.lower(), txt, path + (domain,))
    return total


DNS = {
    "shop.example": ["v=spf1 mx include:_spf.mailer.example include:_spf.crm.example ~all"],
    "_spf.mailer.example": ["v=spf1 include:_spf.cloud.example ip4:192.0.2.0/24 ~all"],
    "_spf.crm.example": ["v=spf1 include:_spf.cloud.example a ~all"],
    "_spf.cloud.example": ["v=spf1 include:a.cloud.example include:b.cloud.example include:c.cloud.example ~all"],
    "a.cloud.example": ["v=spf1 ip4:198.51.100.0/24 ~all"],
    "b.cloud.example": ["v=spf1 ip4:203.0.113.0/24 ~all"],
    "c.cloud.example": ["v=spf1 ip6:2001:db8::/32 ~all"],
}

print("shop.example needs", count_lookups("shop.example", lambda d: DNS.get(d, [])), "lookups")

DNS["_spf.mailer.example"] = ["v=spf1 include:shop.example ~all"]
try:
    count_lookups("shop.example", lambda d: DNS.get(d, []))
except ValueError as error:
    print(error)

Той отпечатва shop.example needs 12 lookups: една за mx, по една за двата include, а после и услугата за бюлетини, и CRM системата включват един и същ облачен доставчик, чийто запис има три свои include. Този общ доставчик се брои два пъти, защото получателят го проверява два пъти; това не е цикъл. Истински цикъл е запис, който води обратно до себе си, и след промяната в края примерът казва loop: shop.example > _spf.mailer.example > shop.example. За истински домейн подайте функция txt, която пита DNS.

Начини да се върнете под десет, по реда, в който ги препоръчваме: махнете include записите за услуги, които вече не ползвате; заменете a и mx с адресите ip4 и ip6 на собствените си сървъри, които не струват нищо; и изпращайте масовата поща от поддомейн като news.shop.example, който има свой SPF запис и свои десет заявки. Копирането на адресите на доставчика в записа ви („сплескване“) също работи, докато доставчикът не ги смени, без да ви каже.

#DMARC: от наблюдение към защита

SPF и DKIM казват дали писмото е дошло от разрешен сървър или носи валиден подпис. DMARC добавя частта, която хората виждат: домейнът в реда „От“ трябва да съвпада с домейна, който е минал проверката. Записът му казва какво да прави получателят с пощата, която не минава: p=none само докладва, p=quarantine я праща в спам, а p=reject я отказва. На адреса в rua= идват ежедневни обобщени отчети от големите пощенски доставчици със списък на всеки сървър, изпратил поща от името на домейна ви.

Указанията на Google за подателите искат от всеки подател SPF или DKIM, а от всеки, който изпраща повече от 5000 писма на ден до Gmail, SPF, DKIM и DMARC запис, за който p=none е достатъчно. Само че p=none не защитава никого. Сигурният път е да започнете с p=none и адрес в rua=, да четете отчетите няколко седмици, докато всеки законен подател минава, после да преминете към quarantine, а по-късно към reject. Поддомейн без собствен DMARC запис попада под записа на основния домейн, с sp= като своя политика, когато този таг е зададен.

#CAA: кой може да издава сертификатите ви

CAA записът изброява удостоверяващите органи, които могат да издават сертификати за домейна, например 0 issue "letsencrypt.org", а за сертификати със звездичка се ползва issuewild. Удостоверяващите органи трябва да го проверят, преди да издадат. Проверката се изкачва нагоре по дървото (RFC 8659): за www.shop.example органът първо гледа това име и, ако то няма CAA записи, гледа shop.example, а решава първото име, което има записи. Без CAA запис всеки орган може да издава. С такъв запис органът, който реално ползвате, трябва да е в него: преместване при хостинг, който ползва друг орган, плюс CAA запис, писан за стария, е класически начин да изгубите подновяване.

#Уеб страната в същата проверка

Щом гледате отвън, проверете и входната врата: http:// трябва да води към https:// с едно пренасочване; сертификатът трябва да има повече от три седмици валидност и да отговаря на името; TLS 1.0 и 1.1 трябва да се отказват; HTTPS отговорът на самия хост трябва да носи Strict-Transport-Security; а /.well-known/security.txt трябва да дава Contact: и бъдеща дата в Expires:, както иска RFC 9116.

#Инструмент, който проверява всичко това по график

Направихме Gatepost, инструмент за команден ред, който проверява домейн така, както го виждат тези сървъри: DNS и CAA с наследяване от основния домейн, MX и нулев MX, един валиден SPF запис с проследен всеки include и преброени десетте заявки, DMARC заедно с sp= на основния домейн, сертификата и версиите на TLS, пренасочването, защитните заглавки, флаговете на бисквитките и security.txt. Всеки резултат има код и тежест, отчетът е таблица, JSON, Markdown или CSV, а кодовете на изход го правят нощна задача или стъпка в CI за списък с домейни. Тестовете му се пускат срещу локални DNS и HTTPS сървъри, а е стартиран само файлът за Linux. Брои заявките така, както ги броят получателите: доставчик, включен от две места, като общия облачен запис тук, се брои всеки път, така че за shop.example съобщава 12 заявки и никакъв цикъл. Истинският цикъл се показва с веригата си, а повече от две празни заявки (void lookups) също се отбелязват. Инструментът е обявен като „Очаквайте скоро“.

#Често задавани въпроси

Защо SPF записът ми се проваля, когато изглежда наред?

Обикновено защото има нужда от повече от десет DNS заявки, след като се проследи всеки include, или защото домейнът има два записа, които започват с v=spf1. И двете дават постоянна грешка, каквото и да казва записът.

С ~all или с -all да завършва SPF?

И двете са ясна политика. Когато има DMARC, получателите действат по политиката на DMARC, затова ~all е обичайният избор, докато още откривате всичките си податели; -all е правилно за домейн, който не изпраща поща.

Достатъчно ли е p=none в DMARC?

Покрива изискването на големите пощенски доставчици и ви дава отчети, но не спира никого да изпраща поща от името на домейна ви. Приемете го като първа стъпка към quarantine и reject.

Трябва ли ми CAA запис?

Не, но той ограничава кой може да издава сертификати за домейна ви. Ако добавите такъв, изброете всеки орган, който ползвате, включително този, с който хостингът или CDN услугата ви издава автоматичните сертификати.

#Източници

Още по темата

Още по темата

Коментари

Коментари

Напишете първия коментар.

Оставете коментар

Следваща стъпка

pnscripts.com/bg/contact

Пишете на екипа

Попитайте за статия или ни разкажете за проект, който искате да изградим. Отговаряме до един работен ден.

Пишете ни

Семейството PN Scripts

Другите сайтове на PN Scripts

Хостингът, игрите и блогът имат свои сайтове, поддържани от същата компания.

  • pnscripts.com

    PN Scripts

    Софтуерно инженерство

    Уеб и мобилни приложения, API и игри по поръчка, плюс нашите продукти и плъгини с отворен код.

  • games.pnscripts.com

    Игри

    Игри и игрови сървъри

    Домът на игрите на PN Scripts: безплатни браузърни игри, които се играят веднага, а игровите сървъри предстоят.

  • hosting.pnscripts.com

    Хостинг

    Хостинг и инфраструктура

    Споделен хостинг, KVM VPS, самостоятелни сървъри и домейни от същата компания, която изработва проекта ви.

  • blog.pnscripts.com

    Блог

    Статии и бележки

    Практични статии за разработка на софтуер, хостинг, отворен код и игри, писани от хората, които ги създават.

    Вие сте тук