Прикончить деда: переход с Basic Auth на mTLS
Интро
Как часто вы хотели что-то развернуть и думали, а как прикрыть это от внешнего мира? Я довольно часто.
Есть у меня своего рода фетиш, разворачивать разные сервисы для личных нужд. Например:
- Gitea, для синхронизации Obsidian
- Сhangedetection, для мониторинга изменений на сайтах
- Hive от Hexway, для ведения файдингов на багбанти
- n8n для автоматизаций
и многое другое что можно найти в репозитории awesome-selfhosted
Если в части сервисов у меня нет важной информации, то с Hive ситуация совершенно иная и его необходимо прикрыть дополнительным слоем защиты.
Первым на ум приходил старик Basic Auth. Но он подкидывает проблем тк подвергается бруту да и передается через заголовки преобразованных в base64.
В один прекрасный момент я вспомнил, что еще в далеком 2014 настраивал доступ к сервису RoundCube (Web интерфейс для работы с почтой) через mTLS.
Кто же такой mTLS?
Простым языком, это проверка сертификата с двух сторон. Не только Вы проверяете, что сервер имеет нужный сертификат, но и сам сервер запрашивает ваш клиентский сертификат для проверки и предоставления доступа.
Зачем это все нужно
Могу отметить следующие плюсы:
- Это удобно, вам не потребуется дополнительно вводить логин и пароль от Basic Auth
- Если Вы по какой-то причине не умели в сертификаты, у вас появится https вместо http.
- Вам перестанут брутфорсить Basic Auth
- Логин и пароль проще украсть нежели сертификат, следовательно это безопаснее
Процесс настройки
Не пугайтесь, да этот процесс сложнее Basic Auth, но я опишу все под ctrl+c, ctrl+v.
Так же в этой статье не будет страшных слов, таких как Vault от Hashicorp, все постараюсь описать максимально просто.
Для ознакомления с механизмом, пример будет с использованием самоподписанного сертификата, где корневой центр сертификации и сервис доступный через mTLS размещены на одном сервере.
Серверная сторона
Представим, что у вас есть сервер доступный через интернет с Ubuntu Linux на борту.
Преднастройка
ПО
Если у вас уже установлен nginx и openssl - хорошо. Если нет, то:
sudo apt install openssl nginx sudo systemctl enable nginx sudo service nginx start
Конечно nginx у нас может быть в docker, но сегодня мы рассмотрим распространненную ситуацию, без глубокого погружения в сети и конфигурирования взаимодействия между docker контейнерами.
Развернем тестовое приложение docker:
sudo docker run -d --rm -p 127.0.0.1:3000:3000 bkimminich/juice-shop
Если docker не установлен, то вам сюда https://docs.docker.com/engine/install/ubuntu/
После выполнения команды будет запущен Juice Shop на http://127.0.0.1:3000
Центр Сертификатов
Создание каталогов для работы с сертификатами:
sudo mkdir -p /etc/ssl/mtls/ca/{certs,crl,newcerts,private}
sudo chmod 700 /etc/ssl/mtls/ca/private
sudo touch /etc/ssl/mtls/ca/index.txtСоздадим индекс файл для хранения информации о сертификатах:
sudo touch /etc/ssl/mtls/ca/index.txt
Создадим файлы с id от которых пойдет отсчет наших выданных и отозванных сертификатов (исторически id от 1000):
sudo echo 1000 > /etc/ssl/mtls/ca/crlnumber sudo echo 1000 > /etc/ssl/mtls/ca/serial
Создадим свой файл конфигурации openssl:
sudo vim /etc/ssl/mtls/openssl.cnf
(!) Обратите особое внимание на блок [ alt_names ], в примере ниже использован мой домен, его 100% необходимо заменить на ваши данные
[ ca ] default_ca = CA_default # Указываем данные о местонахождении файлов и параметры для выпуска CA # в данном примере CA выпускается на 10 лет [ CA_default ] dir = /etc/ssl/mtls/ca certs = $dir/certs crl_dir = $dir/crl database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/cacert.pem serial = $dir/serial crlnumber = $dir/crlnumber crl = $dir/crl.pem private_key = $dir/private/cakey.pem default_md = sha256 default_days = 3650 policy = policy_match default_crl_days = 30 # Настройки политик проверки полей в выдаваемых сертификатах # должны совпадать по countryName, stateOrProvinceName, organizationName [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match commonName = supplied # Настройки для запросов создания сертификатов [ req ] default_bits = 4096 default_md = sha256 distinguished_name = req_distinguished_name x509_extensions = v3_ca # Описание формата DN (distinguished name) при создании сертификатов [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = RU stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Saint Petersburg localityName = Locality Name (eg, city) localityName_default = Saint Petersburg organizationName = Organization Name (eg, company) organizationName_default = Evil Corp commonName = Common Name (eg, YOUR name) commonName_default = Put new data here (CA or dns or user) # Дополнительная конфигурация для CA сертификатов # basicConstraints = CA:TRUE - параметр, который позволяет сертификату # подписывать иные сертификаты # keyUsage - может использоваться для подписания сертификатов и CRL (Отзыв сертификатов) # subjectKeyIdentifier - идентификатор будет хэшом # authorityKeyIdentifier: # keyid:always - всегда включать идентификатор CA ключа в сертификат # issuer - Добавление в сертификат информации о DN CA сертификата [ v3_ca ] basicConstraints = CA:TRUE keyUsage = keyCertSign, cRLSign subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer # Дополнительные опции для реквеста сертификата server [ v3_req ] basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = serverAuth authorityKeyIdentifier = keyid,issuer subjectAltName = @alt_names # Важное поле в котором перечисляются домены и IP в поле Alternative Name # У меня wildcard сертификат, но можно перечислить имена и по одному # Если планируется сертификат для взаимодействия по IP, то # указать это нужно в формате IP.1 = ваш_ip следующей строкой можно # добавить еще адрес написав IP.2= ваш_второй_ip [ alt_names ] DNS.1 = *.0q.lol # Дополнительные опции реквеста для сертификатов клиента # Обратите внимание на параметр extendedKeyUsage, здесь он clientAuth # это означает, что сертификаты выпущенные с этим блоком настроек # будут передаваться для аутентификации клиента [ v3_client ] basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = clientAuth authorityKeyIdentifier = keyid,issuer
Создание сертификатов
CA cert
Создание ключа корневого сертификата:
Во многих статьях показан процесс сначала генерации ключа, затем выпуска сертификата. Но я человек ленивый и делаю все в одном
(!) Потребуется ввести пароль, это самый главный пароль который стоит хорошо спрятать и не потерять он на потребуеться довольно часто.
(!) Будет интерактивное меню ввода данных, часть из них можно предзаполнить в нашем openssl.cnf
(!) В данном случае commonName можно заполнить как угодно, например Evil Corp CA
sudo openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \ -keyout /etc/ssl/mtls/ca/private/cakey.pem \ -out /etc/ssl/mtls/ca/cacert.pem \ -config /etc/ssl/mtls/openssl.cnf
Сертификат сервера
Сертификат сервера (server.crt) в данном случае, это сертификат который будет закреплен за IP или dns нашего сервиса.
Ключ сервера можно сделать слабее так как он в отличии от CA будет с меньшим сроком жизни
Создание ключа и запроса сервера на подписание нашего сертификата, корневым сертификатом:
sudo openssl req -new -newkey rsa:2048 -nodes \ -keyout /etc/ssl/mtls/server.key \ -out /etc/ssl/mtls/server.csr \ -config /etc/ssl/mtls/openssl.cnf
Теперь подпишем сертификат сервера:
(!) Будет запрос на ввод пароля, того самого который использовался для ключа корневого сертификата.
(!) При заполнении commonName необходимо указать ваш IP (если у вас нет домена) или dns имя которое будет в дальнейшем использоваться. В моем случае я указал *.0q.lol.
Обратите внимание, что используется ключ -extensions v3_req, это указывает на раздел применяемых настроек из нашего openssl.cnf
sudo openssl ca -config /etc/ssl/mtls/openssl.cnf \ -in /etc/ssl/mtls/server.csr \ -out /etc/ssl/mtls/server.crt \ -extensions v3_req -days 365
Сертификат клиента
Создание ключа и запроса клиента на подписание нашего сертификата, корневым сертификатом:
sudo openssl req -new -newkey rsa:2048 -nodes -keyout /etc/ssl/mtls/client.key \ -out /etc/ssl/mtls/client.csr \ -days 365 \ -config /etc/ssl/mtls/openssl.cnf
Теперь подпишем сертификат клиента:
(!) Будет запрос на ввод пароля, того самого который использовался для ключа корневого сертификата.
(!) При заполнении commonName необходимо имя клиента, например ник.
Обратите внимание, что используется ключ -extensions v3_client, это указывает на раздел применяемых настроек из нашего openssl.cnf
sudo openssl ca -config /etc/ssl/mtls/openssl.cnf \ -in /etc/ssl/mtls/client.csr \ -out /etc/ssl/mtls/client.crt \ -extensions v3_client -days 365
Теперь для удобства использования можно объединить ключ клиента и его сертификат:
(!) При экспорте будет запрошен пароль для шифрования наших данных внутри client.p12, он еще пригодиться при настройке клиента.
Вы так же наверняка заметите странный ключ legacy. Данный ключ нужен, чтобы охватить большее число клиентов, которые смогут понять этот сертификат, например MacOS.
Ключ name в некоторых системах позволяет визуально выделить сертификат среди других.
sudo openssl pkcs12 -export -legacy -in /etc/ssl/mtls/client.crt \
-inkey /etc/ssl/mtls/client.key -name ClientName \
-out /etc/ssl/mtls/client.p12После того как у нас есть client.p12, можно удалить client.crt и client.key
Список отозванных сертификатов (CRL)
Мы должны создадать специальный файл со списком отозванных сертификатов, чтобы сервер мог проверить например, не отозван ли сертификат клиента.
sudo openssl ca -config /etc/ssl/mtls/openssl.cnf \ -gencrl -out /etc/ssl/mtls/ca/crl.pem
Цепочка сертификатов (Full Chain)
Для доверия нашему CA нам потребуеться на машине клиента помимо клиентского сертификата, так же установить и цепоцку сертификатов server + CA
Это делается элементарно просто
sudo cat /etc/ssl/mtls/server.crt \
/etc/ssl/mtls/ca/cacert.pem > /etc/ssl/mtls/full_chain.pemОтзыв сертификата
Далеко не уходя, чтобы отозвать сертификат, который вам больше не нужен, можно воспользоваться командой:
sudo openssl ca -config /etc/ssl/mtls/openssl.cnf \ -revoke /etc/ssl/mtls/client.crt
Если же у нас не сохранился сертификат на сервере, что после конечной настройки будет правильным путем, то можно отозвать по серийному номеру.
Для этого можем посмотреть, какие сертификаты у нас есть в index'е:
sudo vim /etc/ssl/mtls/ca/index.txt
Затем отозвать подставив серийный номер:
sudo openssl ca -revoke -serial <серийный_номер> \
-config /etc/ssl/mtls/openssl.cnf ВАЖНО! Не забудте пересоздать crl.pem и перезапустить nginx
sudo openssl ca -config /etc/ssl/mtls/openssl.cnf \ -gencrl -out /etc/ssl/mtls/ca/crl.pem sudo nginx -t && sudo nginx -s reload
Настройка nginx
Теперь настроим nginx для работы с сертификатами и нашим сервисом на http://127.0.0.1:3000
Первым делом отредактируем основной файл конфигурации nginx:
sudo vim /etc/nginx/nginx.conf
В секции http раскомментируем строчку "server_tokens off;"
Данный параметр отвечает за отключение заголовков версии nginx, мы же не хотим чтобы весь интернет знал под какую версию искать CVE =)
Далее создадим конфигурацию для сервиса:
sudo vim /etc/nginx/sites-available/mtls
server {
listen 80;
server_name mtls.0q.lol;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name mtls.0q.lol;
ssl_certificate /etc/ssl/mtls/server.crt;
ssl_certificate_key /etc/ssl/mtls/server.key;
ssl_client_certificate /etc/ssl/mtls/ca/cacert.pem;
ssl_verify_client on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_crl /etc/ssl/mtls/ca/crl.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}Создадим ссылку для "включения" конфигурации сервера nginx
sudo ln -s /etc/nginx/sites-available/redirects.conf /etc/nginx/sites-enabled/redirects.conf
Теперь питание компьютера можно отключить можно перезапустить nginx:
sudo nginx -t && sudo nginx -s reload
Настройка клиента
Перемещение на клиента
Для клиента нам потребуется стянуть сертификаты с сервера, сделать это можно например так:
sudo cp /etc/ssl/mtls/{client.p12,full_chain.pem} ~/
sudo cp /etc/ssl/mtls/ca/cacert.pem ~/Теперь для просто ты перемещения сожмем
sudo zip -r client.zip client.p12 full_chain.pem cacert.pem sudo chown <юзер>:<юзер> client.zip
И переместим на свою машину например с помощью scp
scp <юзер>@<хост>:/home/<юзер>/client.zip /<путь куда сохранить>/
Установка на клиенте
На клиенте расспаковываем архив и определяем в чем же мы хотим использовать сертификат.
О чем это я, нам предстоит импортировать сертфикаты и выставить доверие. Для этого необходимо поместить их в хранилище сертификатов, а оно у некоторого ПО свое.
Например, firefox может не работать с системным хранилищем, в то время как chrome работает только с системным.
Поэтому, вынесем настройку firefox отдельно =)
Firefox
Открыв настройки в firefox, воспользуйтесь поиском (так быстрее), введите серт или cert в зависимости от используемой локали.
Если у вас включен third-party root certificates, то сертификаты будут подтягиваться с системного хранилища сертификатов.
Если же такой настройки нет или она по неким соображениям выключена, то открываем просмотр сертификатов
Далее идем во вкладку корневых центров сертификаци и импортируем наш сертификат cacert.pem:
И переходим во вкладку клиентских сертификатов, так же импортируем и вводим пароль от p12:
Все, теперь при переходе на ваш сайт, будет всплывающее окно с выбором сертификата доступа, после подтверждения вы и только вы попадете внутрь.
MacOS
Для настройки нам потребуется запустить Keychain Access
В нем переходим в System - Certificates, после чего в верхнем меню выбираем File - Import Item, и загружаем fullchain.pem
Далее надо установить доверие корневому сертификату, выбираем наш корневой сертификат и открываем его. Далее разворачиваем блок "Trust" и выставляем "Always Trust".
После этого наш второй сертификат (сервера), который так же поместился в хранилище, автоматически станет доверенным.
Ровно таким же образом загружается и client.p12
Думали на этом все? Не тут то было.
В MacOS если вы хотите дать доступ до сертификата приложениям и не хотите каждый раз вводить пароль необходимо проделать еще несколько шагов.
Находим наш клиентский сертификат и раскрываем его. Далее заходим в приватный ключ и переходим в меню Access Control. Тут можно либо выбрать список приложений, которые имеют доступ к ключу и включить/выключить опцию доступа без пароля, либо разрешить всем приложениям иметь доступ к ключу.
Все, теперь браузеры такие как chrome или safari смогут использовать сертификат.
Windows
Тут даже без скриншотов =) Открываем сертификат p12 и импортируем, и импортируем cacert.pem в хранилище доверенных сертификатов.
Burp
Переходим в настройки Burp - Network - TLS - Client TLS Certificates -Add
Указываем домен или IP, кликаем далее и загружаем наш p12
Footer
Предлагайте идеи, буду рад почитать и написать что-нибудь.
Сделать это можно не только в комментарии тут, но и в Telegram канале https://t.me/lazysec