1. 1
  2. 2
  3. 3
  4. 4

# теория · шаг 1 из 4

Кто ответит на неизвестное имя

Коротко

Запрос с незнакомым Host не отбрасывается - его получает первый блок на этом адресе и порту. То есть, скорее всего, твой настоящий сайт.

  • Управляется флагом default_server: он назначает блок-заглушку явно, один раз на каждый адрес и порт.
  • server_name _; - не «любое имя», а просто заведомо невозможное имя.
  • return 444 закрывает соединение молча: ни заголовков, ни тела.
  • На 443 заглушка нужна тоже, и с nginx 1.19.4 ей не нужен сертификат: ssl_reject_handshake on; обрывает рукопожатие, не показав чужому домену вообще ничего.

Если catch-all и ssl_reject_handshake уже стоят - листай до «Теперь сам».

Сначала ответь сам

Твой сервер по адресу 203.0.113.7 (адрес для примера), на нём живёт shop.local. Приходит запрос:

↳  вывод
GET / HTTP/1.1
Host: totally-unrelated-domain.com

Кто-то направил свой домен на твой адрес - по ошибке, ради эксперимента или намеренно. Что ответит nginx?

Инстинктивный ответ - «ничего, имя же не совпало». Настоящий: ответит первый блок на этом порту, то есть твой магазин. Он отдаст свои страницы под чужим доменом, и в поиске может завестись копия сайта, которой ты не управляешь.

На что это похоже

Это дежурный, которому не сказали, что делать с посетителями без пропуска.

В инструкции написано, кого пускать: список фамилий. Про остальных не написано ничего - и дежурный поступает так, как поступают в отсутствие правила: отправляет человека к первой двери по коридору. Не к правильной, не к охраняемой - к первой.

Пока все приходят по списку, это незаметно. Заметно становится в тот день, когда за первой дверью оказывается бухгалтерия.

Механизм: полный алгоритм выбора блока

Собираем всё, что было в разделе, в одну картину.

1. адрес и порт соединения 2. более точный адрес 3. четыре ступени имени нет 4. блок по умолчанию с флагом default_server, а без флага - первый Блок по умолчанию срабатывает ВСЕГДА, когда имя не совпало. Назначил ты его сам или он достался по порядку - вопрос только в этом. У каждого адреса и порта свой блок по умолчанию - и это ловушка.
Не совпало ни одно имя - запрос уходит в блок по умолчанию, четвёртый шаг алгоритма (не путать с четвёртой ступенью имён из прошлого урока). Он не исчезает: у каждой пары «адрес и порт» есть блок по умолчанию, назначенный флагом или доставшийся по порядку.

Сама заглушка выглядит так:

/etc/nginx/conf.d/00-default.confhttp
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

default_server назначает блок по умолчанию явно. Флаг ставится один раз на адрес и порт; второму блоку nginx скажет a duplicate default server for 0.0.0.0:80.

server_name _; - не «любое имя» и не служебный символ. Подчёркивание - обычное имя, которое заведомо не совпадёт ни с одним настоящим доменом. Годится любая невозможная строка, _ прижилось как соглашение.

return 444 - нестандартный код самого nginx: закрыть соединение, не отвечая. Сканер не получает ни заголовков, ни тела, ни повода возвращаться. Такую заглушку ещё называют catch-all - «ловит всё».

Правило

У каждой пары «адрес и порт» есть блок по умолчанию. Не назначил его флагом default_server - им стал первый блок в собранном конфиге. Заглушка нужна на каждый адрес и порт, где ты слушаешь.

Разбор: как выглядит работающая заглушка

Проверено на nginx 1.31.5, коды curl настоящие.

/etc/nginx/conf.d/00-default.confhttp
server { listen 80 default_server; server_name _; return 444; }
server { listen 80; server_name shop.local; return 200 "магазин\n"; }
$  команда
for h in shop.local chuzhoy.local; do printf '%-14s -> ' $h; curl -s -H "Host: $h" localhost/ || echo "curl завершился с кодом $?"; done
↳  выводfor h in shop.local chuzhoy.local; do printf '%-14s -> ' $h; curl -s -H "Host: $h" localhost/ || echo "curl завершился с кодом $?"; done
shop.local     -> магазин
chuzhoy.local  -> curl завершился с кодом 52

|| выполняет вторую команду, только если первая завершилась неудачей, а $? - её код. Пятьдесят второй код curl - «пустой ответ от сервера». Клиент соединился и не получил ничего: ни строки статуса, ни заголовков. Для сканера это тупик, для человека выглядит как «сервер не отвечает» - поэтому иногда вместо 444 отдают честный 404. Вопрос того, кого ты ждёшь по этому адресу.

Отдельная история - 443-й порт. Соединение там начинается с TLS-рукопожатияРазбирается в разделе 9, глава «Сертификат, цепочка и ключ»: Рукопожатие: что происходит до первого запроса - договора о шифровании, и сертификат сервер предъявляет до того, как увидит Host. Просто написать listen 443 ssl в заглушке не выйдет:

↳  выводnginx -t
nginx: [emerg] no "ssl_certificate" is defined for the "listen ... ssl" directive

Раньше на это отвечали «положи туда сертификат основного сайта». С nginx 1.19.4 есть ответ лучше:

/etc/nginx/conf.d/00-default.confhttp
server {
    listen 443 ssl default_server;
    server_name _;
    ssl_reject_handshake on;
}

Сертификат этому блоку не нужен вовсе, и чужой домен не увидит твой:

$  команда
openssl s_client -connect localhost:443 -servername chuzhoy.example.com </dev/null
↳  выводopenssl s_client -connect localhost:443 -servername chuzhoy.example.com </dev/null
40076113577F0000:error:0A000458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name:../ssl/record/rec_layer_s3.c:1593:SSL alert number 112
no peer certificate available

openssl s_client - программа, которая открывает TLS-соединение как браузер: -connect - куда, -servername - какое имя сайта назвать, </dev/null - ничего не отправлять и сразу завершиться. Из длинной распечатки здесь две строки. Рукопожатие обрывается предупреждением с кодом 112 - unrecognized name, «такого имени здесь нет», - и сертификат не предъявлен: no peer certificate available. Вариант с чужим сертификатом показывал бы имя твоего сайта каждому, кто направил на тебя домен; этот не показывает ничего.

Разбор: где наивное правило подводит

Наивное правило: «поставил default_server - закрыл сервер».

Блок по умолчанию свой у каждой пары «адрес и порт», а listen 80 и listen 127.0.0.1:80 - разные адреса. Более точный побеждает, и заглушка на 0.0.0.0:80 до такого соединения не доходит.

/etc/nginx/conf.d/00-default.confhttp
server { listen 80 default_server; server_name _; return 444; }
server { listen 127.0.0.1:80; server_name admin.local; return 200 "внутренняя админка\n"; }
server { listen 80; server_name shop.local; return 200 "магазин\n"; }
↳  выводcurl -s -H 'Host: chuzhoy.local' http://127.0.0.1/
внутренняя админка

Заглушка на месте, флаг стоит, nginx -t доволен - и запрос с чужим именем получил внутреннюю панель. С Host: shop.local на 127.0.0.1 замер тоже отдаёт админку: магазин слушает listen 80, но у более точного адреса свой набор кандидатов. На адресе 127.0.0.1:80 блок ровно один, значит он же и блок по умолчанию, хотя default_server ему никто не писал.

Опасно тут не то, что панель доступна с самого сервера, а то, что правило общее: любой блок с более точным адресом заводит собственный блок по умолчанию. Проверять заглушку надо на том адресе, где ждёшь чужие запросы, а не на localhost.

Что ломается без этого

  • Сайт открывается по чужому домену. Нет заглушки, отвечает первый блок. Проверка - curl -sI -H "Host: definitely-not-your-domain.com" http://твой-адрес/: работающая заглушка даёт пустоту и код завершения curl 52, а лежащий сервер - 7, «не удалось соединиться».
  • В поиске появилась копия сайта на незнакомом домене. То же самое, только замеченное поздно.
  • По https чужому домену показывается твой сертификат. В заглушке на 443 лежит настоящий сертификат вместо ssl_reject_handshake on.
  • a duplicate default server for 0.0.0.0:80. Флаг поставлен дважды на один адрес и порт. Оставь у одного блока.
  • Заглушка не срабатывает на одном из адресов. Ищи блок с более точным listen - у него свой блок по умолчанию.

Зачем это в работе

Сервер с публичным адресом получает запросы с чужими именами постоянно: по старым записям DNS, от сканеров, от чужих доменов, которые кто-то направил на твой адрес. Без заглушки это не просто шум.

Три следствия, ради которых заглушку и ставят. Чужой домен показывает твой сайт - и поисковик может решить, что копия и есть оригинал. Логи заполняются запросами к сайту, который ты не заводил, и разбирать инцидент по ним тяжелее. И сканер, получивший осмысленный ответ, возвращается снова, а получивший пустоту - обычно нет.

Заглушку кладут в файл с именем вроде 00-default.conf. Причина ровно та, что разбиралась в разделе про конфиг: файлы из conf.d/*.conf подключаются по алфавиту, а этот блок должен быть первым - тогда он окажется блоком по умолчанию даже там, где флаг забыли поставить.

Теперь сам

1. Сколько блоков-заглушек нужно серверу, который слушает 80 и 443 по IPv4 и IPv6?

Одного блока достаточно, но строк listen в нём четыре, каждая с флагом default_server:

/etc/nginx/conf.d/00-default.confhttp
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    ssl_reject_handshake on;
    return 444;
}

Проверено: nginx -t принимает, чужое имя по HTTP получает пустоту. Блок по умолчанию свой у каждой пары «адрес и порт», а не у сервера целиком.

2. Почему server_name _; вообще нужно писать, если блок и так помечен default_server?

nginx его не требует - выбор делает флаг, и блок выше без server_name проверку проходит. Пишут его для людей: без него непонятно, случайно этот блок оказался умолчательным или намеренно. Подчёркивание читается как «сюда не приходят по имени».

3. Чем ssl_reject_handshake on; лучше сертификата основного сайта в заглушке?

Тем, что чужой домен не получает вообще ничего: рукопожатие обрывается до предъявления сертификата. С чужим сертификатом любой, кто направил на тебя домен, видит в браузере имя твоего сайта в предупреждении о несовпадении - то есть узнаёт, что ещё живёт на этом адресе.

Главное

Назначай поведение для неизвестного имени на каждом слушающем адресе и порту. Иначе запрос получит первый подходящий серверный блок, которым может оказаться рабочий сайт. Для HTTPS отказ незнакомому SNI выполняется на этапе рукопожатия; HTTP-ответ появляется уже после него.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.