Мы закрывали замечание аудитора: часть внутреннего сервиса отвечала по HTTP. Поставили редирект на HTTPS, показали, что по http:// теперь приходит 301, замечание сняли.
Через несколько недель я вернулся к этому же сервису с другой задачей и посмотрел на редирект внимательнее. Запрос, который получает 301, - это полноценный HTTP-запрос. Он уже ушёл. В нём был путь, параметры и заголовок Cookie.
Редирект не отменяет запрос. Он отвечает на него.
Дальше я полез проверять, кто вообще в наше время попадает на этот редирект, - и картина оказалась интереснее, чем я думал.
Кто ещё шлёт открытый запрос
Первое, что я предполагал: человек набирает example.com без схемы, браузер идёт по HTTP, получает 301. Так было раньше. Сегодня это неверно: Chrome подставляет https:// в адресной строке с версии 90 и с версии 115 повышает до HTTPS обычные переходы, Firefox и Safari делают то же самое. Первый запрос современного браузера уходит по HTTPS, а на HTTP он откатывается, только если по HTTPS не вышло.
Значит, открытый запрос шлют другие: сам этот откат при неудаче; ссылки, в которых схема написана явно как http:// - в письмах, в документации, в закладках; клиенты без такой логики - curl, скрипты, встроенные вебвью в приложениях, старые устройства; и интеграции, которые кто-то настроил на http:// и с тех пор не трогал.
Это и есть окно, которое закрывают HSTS и предзагрузка. Оно уже, чем было пять лет назад, но оно открыто, и вот что через него уезжает:
Полный путь и строка запроса. Адрес страницы говорит больше, чем кажется: /orders/48213/invoice - это не просто «сайт магазина».
Cookie без флага Secure. Флаг Secure означает «не отправлять по незашифрованному соединению». Его ставят не всегда, и без него сессионная кука уезжает в открытом виде вместе с первым же запросом.
Сам факт обращения. Домен виден наблюдателю и при HTTPS, а вот путь запроса - уже нет.
$ curl -s -o /dev/null -D- http://github.com/ HTTP/1.1 301 Moved Permanently Content-Length: 0 Location: https://github.com/
Ответ правильный. Вопрос в том, что произошло до него.
Тот, кто находится в том же канале - открытый Wi-Fi, скомпрометированный домашний роутер, промежуточный узел, - может не только прочитать этот запрос, но и ответить на него вместо сервера: отдать свою страницу или редирект на похожий домен. Проверить подлинность такого ответа клиенту нечем: TLS в этом обмене ещё не начался.
HSTS закрывает второй визит, не первый
Заголовок Strict-Transport-Security предписывает браузеру обращаться к этому домену только по HTTPS - и указывает, сколько секунд это правило действует.
$ curl -s -o /dev/null -D- https://github.com/ | grep -i strict strict-transport-security: max-age=31536000; includeSubdomains; preload
max-age=31536000 - год. После первого успешного захода по HTTPS браузер запоминает правило и дальше подставляет https:// сам, даже если пользователь набрал http://. Никакого открытого запроса больше не будет.
Ключевые слова - «после первого». Заголовок приезжает по HTTPS, а чтобы его получить, надо сначала попасть на HTTPS. Первый визит с нового устройства, из нового браузера, после очистки данных сайта не защищён по определению.
Директива includeSubDomains распространяет правило на все поддомены. Без неё правило действует ровно на тот хост, который его прислал, и mail.example.com остаётся открытым, даже если example.com закрыт. С ней - накрываются все поддомены разом, включая те, о которых вы забыли, и те, которых ещё нет. Это её главное свойство и главная опасность одновременно.
Предзагрузка закрывает и первый визит
Список предзагрузки - это перечень доменов, зашитый прямо в браузер. Для домена из него правило HTTPS действует заранее, и открытого запроса не бывает вообще.
Условия входа: базовый домен отдаёт по HTTPS заголовок с max-age не меньше года, с includeSubDomains и с preload; сертификат валиден; а запрос на 80-й порт редиректом уходит на HTTPS того же хоста. Именно из-за последнего условия заявки и отклоняют чаще всего: схема http://example.com → https://www.example.com его не выполняет. Статус проверяется запросом:
$ curl -s 'https://hstspreload.org/api/v2/status?domain=github.com' { "name": "github.com", "status": "preloaded", "bulk": true, "preloadedDomain": "github.com" }
Подставьте свой домен и посмотрите на status. preloaded - домен в списке. pending - заявка принята, но в браузеры ещё не уехала, то есть защиты пока нет. unknown - домена в списке нет.
Почему я бы не спешил включать предзагрузку
Механизм необратимый по времени. Список едет в браузер вместе с его обновлением - и удаление из списка едет так же.
Порядок выхода такой: убрать preload из заголовка (обычно выставив max-age=0), подать заявку на исключение, дождаться, пока домен переведут в состояние ожидания удаления, а потом - пока обновятся браузеры пользователей. Первые два шага занимают дни, последний - месяцы. Всё это время ваш домен и все его поддомены доступны только по HTTPS.
Ломается на этом обычно не основной сайт, а то, о чём не подумали: служебный поддомен со старым оборудованием, у которого HTTPS нет; внутренний стенд; интеграция с подрядчиком, ходящая на HTTP-ручку. includeSubDomains накрывает их все.
Поэтому порядок такой: сначала убедиться, что по HTTPS работает всё, включая поддомены, которых вы не помните; потом поставить max-age на несколько минут и посмотреть; потом поднять до года; и только потом добавлять preload.
Для сайта, у которого нет ни персональных данных, ни авторизации, я бы ограничился HSTS без предзагрузки. Выигрыш есть только на первом визите и на нестандартных клиентах, а цена ошибки - недоступность на месяцы.
Что стоит проверить заодно
Флаг Secure на всех cookie. Он бесплатный и закрывает главное, что уезжает в открытом виде. Ставится вместе с HttpOnly и SameSite.
Редирект в один шаг. Частая схема - http://example.com → http://www.example.com → https://www.example.com. Здесь два открытых запроса вместо одного, и промежуточный редирект тоже можно подменить. Правильно - сразу на HTTPS того же хоста, а уже оттуда, если нужно, на www. Это же условие требуется для попадания в список предзагрузки.
Ссылки в письмах и в документации. Ссылка с http:// внутри письма - это тот самый первый запрос, и приходит он не от пользователя, набравшего адрес, а от вас.
Ограничения
Ни HSTS, ни предзагрузка не защищают от подмены самого домена: тот, кто увёл человека на похожий домен, получает на него валидный сертификат, и всё описанное работает у него ровно так же, как у вас.
И они ничего не делают с клиентами, которые HSTS не поддерживают: curl без специальных ключей, самодельные скрипты, часть встроенных вебвью. Для них единственная защита - чтобы адрес с http:// вообще нигде не был записан.
Что посмотреть у себя
Запросить http://ваш-домен и посмотреть, приходит ли 301 и куда он ведёт. Отдельно проверить www и все поддомены, которые вы знаете.
Проверить наличие Strict-Transport-Security в ответе по HTTPS и значение max-age. Часто там стоит несколько минут, оставшихся с этапа проверки.
Проверить статус домена в списке предзагрузки запросом к hstspreload.org.
Посмотреть флаги на своих cookie. Secure - это то, что можно поставить сегодня, и оно закрывает большую часть перечисленных последствий.
Комментарии (14)

allter
13.09.2026 09:45Плюс ещё не забывайте про DNS.
Если нужна нормальная защита - то только предустановка клиентского сертификата и информирование пользователя на тему фишинга…
trublast
Спасибо за интересный анализ.
Я может быть добавлю, что несмотря на то, что браузеры теперь НЕ отправляют первый запрос на http, это в целом мало помогает.
Рассмотрим вариант с браузером. Если злоумышленник может перехватить ваш трафик http, то вполне возможно он может заблокировать ваше соединение по https (если вы подключены через его wifi, или послать tcp reset если он просто подключен вместе с вами к этому wifi), и первый запрос браузера сфйелится, ибраузкр вероятно пошлет его по http. А дальше история с фишингом, про который вы уже рассказали.
Дальше тоже интересно. Даже если у вас вообще будет выключен http на вашем сервере, то ничего не заставит упомянутые интеграции, в которых захардкожен доступ по http, не делать этих запросов по http. То есть то, что они отправляют - все равно сможет быть перехвачено.
Поэтому, хотя выглядит так, что жёстким, но безопасным способом было бы вообще выключить http - это не так. Безопаснее было бы ОСТАВИТЬ http включенным на своей стороне (дальше уже или редирект, или дроп) но в этом случае вы хотя бы будете знать, кто к вам ходит по http, и можете считать, что их данные потенциально скомпрометированы. Может быть как-то уведомлять клиентов или партнёров, что нужно произвести перенастройку.
vadimr
Кому нужно? Клиенту, может, не хочется создавать в своём оборудовании точку отказа, связанную с сертификатами. Особенно в наше сложное время.
trublast
Ну это вопрос философский.
Можно провести мысленный эксперимент. Если ваш сервис передает какие-то данные, и вы эти данные параллельно (например, в виде логов) можете выложить в открытом доступе в интернет, то значит вам не нужны сертификаты и TLS. Если не можете публично выложить эти данные - то нужны сертификаты и TLS.
Ведь логика простая. Ваш трафик без проверки сертификата может кто-то прочитать. Не значит, что обязательно прочитает, но может.
Тоже самое с логами обмена в интернете. Если вы их выложили, то это не значит, что их кто-то прочитает. Но может кто-то прочитать.
В обоих случаях это не конкретный субъект, а неопределенная группа субъектов, на которую вы не можете повлиять или даже их как-то идентифицировать.
С логами в интернете даже лучше ситуация. Если они на вашем сервере, и их кто-то получал, вы хотя бы знаете, что их кто-то получал. В случае перехвата http вы даже не знаете, что перехват был.
vadimr
Логично, но может я шифрую на уровне приложения.
В применение https в современном виде заложено слабое место в виде предустановленного удостоверяющего центра, к которому на практике доверие оказывается значительно ниже, чем предполагалось оптимистами.
trublast
Согласен с вами.
И противоречия тут нет. Если вы шифруете на уровне приложения, то данные в нашем гипотетическом логе будут зашифрованы так же. Значит их можно опубликовать - они уже защищены.
vcKomm
Самоподписанные сертификаты?
vadimr
Использование самоподписанного сертификата само по себе не освобождает систему от доверия предустановленным удостоверяющим центрам. Которые, как мы теперь знаем на опыте, по команде от государства способны делать вещи, выходящие за их декларированные функции. А если выковырять из системы ссылки на корневые УЦ, то, боюсь, вообще всё сломается.
trublast
Ну почему же, вполне себе работает без системных сертификатов. Только картинка переворачивается с ног на голову. Вы доверяете своему УЦ, а всем остальным - не доверяете.
Наверное многие сталкивались с таким, если собирали докер образы from scratch, например у вас там один бинарник и всё. Вы можете положить туда только свой СА, и доверие будет только к нему. Но перестанут работать условные соединения в условный тындекс, например connect tyndex.ru 443
Поэтому на практике системные сертификаты оставляют, но для тех соединений, которые к вашим ресурсам, а не к публичным, вы явным образом указываете свой CA. Ваш трафик не перехватят. А если вы переживаете, что трафик до тындекса (из примера) перехватят, так майор может перехватить его по другому, без всякого MITM, просто отправив запрос в тындекс. А может там уже стоит сорм внутри, где tls уже развернули, и можно читать плейнтексты. Но это уже за рамками проблемы, что ваши данные между сервисами могут слить. Если один из таких сервисов сам их сливает, после получения, то нет смысла шифровать канал передачи данных. Точнее надо понима, что если на той стороне их кто-то читает, то это не обязательно тот, кто вы думаете.