“ - Ты staging видишь?
- Нет…
- А он – есть!”
Да мало ли зачем еще надо сделать скрытый сайт, о существовании которого никто даже не сможет предположить.
Пример со staging весьма показателен – на финальном этапе хотелось бы разместить приложение уже на production сервере, чтобы брать реальные данные из боевых баз и подсматривать в боевые кэши-в-памяти. Но очень не хочется, чтобы его увидел кто-то еще, особенно, со стороны. Или админка должна быть доступна во внешнем мире, но как бы ее скрыть от любопытных глаз и сканеров?
Конечно, можно все это ограничить по ip, но он может быть динамическим (сотрудники на удаленке или в командировке). Можно развернуть VPN, но это требует установки дополнительного приложения, да и провайдеры не жалуют специфические протоколы.
Сценарий, при котором появилось это решение – необходимость предоставить возможность приложениям обмена файлами по WebDAV, используя минимум технологий. И с минимальным влиянием на клиентскую часть, для обеспечения совместимости с недостаточно гибкими в настройке инструментами.
Концепция
Мы можем воспользоваться http заголовком “Host”. На стороне сервера это будет “server_name”. Здесь я буду приводить примеры настройки Nginx, но все сказанное одинаково справедливо и для Apache.
Чтобы “никто не догадался”, не будем публиковать соответствующую запись в DNS. Те, кто знает про суслика, добавят его себе в "/etc/hosts". Опять же, примеры приводятся для Linux (POSIX, читай - Mac OS тоже), но в равной степени это будет работать и в Windows.
Дополнительно усложняем себе злоумышленнику жизнь с помощью клиентских SSL сертификатов. При собственном CA получаем защиту от MiTM за счет взаимной аутентификации, а заголовок передается, когда уже действует шифрование трафика и перехватить его не получится.
Реализация
Очевидно, что реальный и скрытый сайты должны быть на одном ip адресе и порту (как минимум, чтобы скрытое имя хоста не светилось в процессе handshake). К сожалению, из-за ограничений https (сервер начинает шуметь первый и раскрывает все свои сертификаты) серверный сертификат должен быть wildcard. К счастью, даже “Let’s encrypt” уже предлагает такую опцию. Будем считать, что у нас уже есть сертификат для “*.mydomain.ltd”. Если же мы говорим о собственном CA, главное не забыть, что обязательный сейчас “subjectAltName” с соответствующим “DNS.x” можно передать OpenSSL только через файл конфигурации, командная строка пока не доросла.
Подготовим парочку клиентов для примера. Напоминаю, что вовсе не обязательно, чтобы и серверные и клиентские сертификаты были подписаны одним и тем же CA. Достаточно рассказать серверу каким клиентам доверять.
Генерируем сертификаты
Создадим самоподписанный корневой CA сертификат с именем secroot, который будет действителен в течение 10 лет. Он будет использоваться для подписи всех других сертификатов.
- openssl genpkey -algorithm RSA -out secroot.key -pkeyopt rsa_keygen_bits:4096
- openssl req -x509 -new -nodes -key secroot.key -sha256 -days 3650 -out secroot.crt -subj "/C=RU/ST=Some-State/L=Some-City/O=MyOrg/OU=MyOrgUnit/CN=secroot"
На ключ ставим права 0600 и никому про него не рассказываем.
Создадим два клиентских сертификата: secuser1 и secuser2 для проверки пользователей, получающих доступ к скрытому сайту. Для каждого клиента создадим отдельный приватный ключ и подпишем его сертификат с использованием нашего корневого.
Пример на два года для secuser1:
- openssl genpkey -algorithm RSA -out secuser1.key -pkeyopt rsa_keygen_bits:2048
- openssl req -new -key secuser1.key -out secuser1.csr -subj "/C=RU/ST=Some-State/L=Some-City/O=MyOrg/OU=Clients/CN=secuser1"
- openssl x509 -req -in secuser1.csr -CA secroot.crt -CAkey secroot.key -CAcreateserial -out secuser1.crt -days 730 -sha256
То же самое делаем для secuser2:
- openssl genpkey -algorithm RSA -out secuser2.key -pkeyopt rsa_keygen_bits:2048
- openssl req -new -key secuser2.key -out secuser2.csr -subj "/C=RU/ST=Some-State/L=Some-City/O=MyOrg/OU=Clients/CN=secuser2"
- openssl x509 -req -in secuser2.csr -CA secroot.crt -CAkey secroot.key -CAcreateserial -out secuser2.crt -days 730 -sha256
«Главное – не бояться». И не перепутать сертификат с приватным ключом. Хотя в случае клиента отдать все равно нужно оба.
Настраиваем сервер
Создадим два виртуальных хоста: один будет работать на “mydomain.ltd” по умолчанию и не потребует проверки клиентского сертификата (вероятно, он у вас уже есть, тогда сразу переходим к «плану Б»), а второй — для домена “secsite.mydomain.ltd”, с обязательной проверкой клиентских сертификатов.
Заметьте, для “secsite.mydomain.ltd” мы даже не будем пытаться слушать 80 порт.
Скрытый сайт будет доступен только тем, кто обладает валидным клиентским сертификатом, выданным нашим корневым CA. Для окончательного введения потенциального неприятеля в заблуждение, можно поменять «ssl_verify_client» на «optional», и в “location” проверить, что «$ssl_client_verify != ‘SUCCESS’» и вернуть грустное «404», вместо подозрительного «401». Вы же справитесь с этим самостоятельно?
Перезапускаем Nginx. Набираем в браузере «mydomain.ltd» и наслаждаемся “/var/www/html/index.html”.
А теперь, добавляем в “/etc/hosts” запись “x.x.x.x secsite.mydomain.ltd”. Набираем «secsite.mydomain.ltd» и ничего не получается, потому что мы не поставили в браузер (на компьютер, телефон, холодильник и утюг) клиентский сертификат.
Возвращаемся в консоль
- openssl pkcs12 -inkey secuser1.key -in secuser1.crt -export -out secuser1.p12
У нас получился pkcs12 (p12) контейнер, в котором сразу и приватный ключ, и сертификат. Именно в таком виде его потребляют операционные системы и браузеры. «Но есть нюанс». С выходом версии OpenSSL 3.x, некоторые «ненадежные» алгоритмы шифрования были исключены из стандартного перечня. Беда в том, что «мужики» из Palo Alto об этом «не в курсе». Поэтому попытка поставить такой контейнер на iPhone (iPad и прочие iOS) приводит к сообщению о неизвестном методе шифрования. Поэтому к команде надо добавить еще и ключ «-legacy» (ну, и “-out secuser1-legacy.p12” сделать, чтобы самим не перепутать).
Устанавливаем сертификат из контейнера на клиентское устройство и видим “/var/www/secsite/index.html”.
Вместо заключения
Методика весьма универсальна, и все, что умеет https, может быть таким образом спрятано. И даже параноидальный отельный WiFi/DPI, у которого запрещено вообще все, кроме “{80, 443}” не заподозрит неладное. Например, можно создать скрытый git-репозиторий.