# теория · шаг 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?
Инстинктивный ответ - «ничего, имя же не совпало». Настоящий: ответит первый блок на этом порту, то есть твой магазин. Он отдаст свои страницы под чужим доменом, и в поиске может завестись копия сайта, которой ты не управляешь.
На что это похоже
Это дежурный, которому не сказали, что делать с посетителями без пропуска.
В инструкции написано, кого пускать: список фамилий. Про остальных не написано ничего - и дежурный поступает так, как поступают в отсутствие правила: отправляет человека к первой двери по коридору. Не к правильной, не к охраняемой - к первой.
Пока все приходят по списку, это незаметно. Заметно становится в тот день, когда за первой дверью оказывается бухгалтерия.
Механизм: полный алгоритм выбора блока
Собираем всё, что было в разделе, в одну картину.
Сама заглушка выглядит так:
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 настоящие.
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
shop.local -> магазин
chuzhoy.local -> curl завершился с кодом 52
|| выполняет вторую команду, только если первая завершилась неудачей, а $?
- её код. Пятьдесят второй код curl - «пустой ответ от сервера». Клиент соединился и не
получил ничего: ни строки статуса, ни заголовков. Для сканера это тупик, для
человека выглядит как «сервер не отвечает» - поэтому иногда вместо 444 отдают
честный 404. Вопрос того, кого ты ждёшь по этому адресу.
Отдельная история - 443-й порт. Соединение там начинается с
TLS-рукопожатияРазбирается в разделе 9, глава «Сертификат, цепочка и ключ»: Рукопожатие: что происходит до первого запроса - договора о шифровании, и
сертификат сервер предъявляет до того, как увидит Host. Просто написать
listen 443 ssl в заглушке не выйдет:
nginx: [emerg] no "ssl_certificate" is defined for the "listen ... ssl" directive
Раньше на это отвечали «положи туда сертификат основного сайта». С nginx 1.19.4 есть ответ лучше:
server {
listen 443 ssl default_server;
server_name _;
ssl_reject_handshake on;
}
Сертификат этому блоку не нужен вовсе, и чужой домен не увидит твой:
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 до такого соединения не доходит.
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"; }
внутренняя админка
Заглушка на месте, флаг стоит, 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:
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-ответ появляется уже после него.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий