Перейти к содержимому

Токены в Url: почему они утекают не через referer, а через логи и аналитику

"Токен в ссылке украдут через Referer" - устарело пять лет назад. Куда он уходит теперь

Совет не передавать токены в URL обычно формулируют так: если секрет окажется в адресе страницы, браузер отправит его сторонним сайтам в заголовке `Referer`. Для современных браузеров это объяснение в большинстве случаев уже не соответствует действительности.

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

Что действительно передаёт браузер

Проверить поведение можно на двух HTTP-серверах. Первый отдаёт страницу, второй имитирует внешний ресурс, например изображение или скрипт. Если открыть страницу по адресу вроде:

```text
https://example.test/reset?token=abc123
```

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

При обращении к другому источнику ситуация иная. При стандартной политике `strict-origin-when-cross-origin` внешний сервер получает только origin:

```text
https://example.test/
```

В нём нет пути `/reset` и параметра `token`.

Такое поведение стало стандартным не недавно. Chrome перешёл на эту политику в версии 85 в августе 2020 года, Firefox - в версии 87 в марте 2021-го. Safari начал сокращать межсайтовый `Referer` ещё раньше, хотя первоначально применял это правило выборочно.

Полный URL при межсайтовом переходе можно вернуть только явно заданной политикой `Referrer-Policy: unsafe-url`. Использовать её для страниц с секретами не следует.

При этом важна деталь: ограничение действует только на заголовок `Referer`. Сам документ, его JavaScript-код, браузерные расширения и серверная инфраструктура продолжают видеть адрес целиком.

1. Серверные access-логи

Самое очевидное место утечки часто остаётся без внимания - журналы веб-сервера. Типовая конфигурация Nginx записывает в лог `$request_uri`, то есть путь вместе со строкой запроса:

```text
/reset?token=abc123
```

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

Срок жизни токена может составлять 15 минут, а срок хранения лога - год или дольше. В результате секрет продолжает существовать уже после того, как перестал быть действительным.

Кроме того, токен может оказаться в логах повторно. Если страница обращается к ресурсу того же источника, полный адрес передаётся ему в `Referer`. Таким образом, параметр присутствует и в URL исходного запроса, и в заголовке последующих внутренних запросов.

2. Аналитика и системы мониторинга

`Referrer-Policy` не защищает от JavaScript, который сам читает адрес страницы. Аналитический счётчик может получить значение через:

```javascript
location.href
```

а затем отправить его в теле собственного запроса.

Похожим образом работают системы сбора ошибок. Вместе с исключением они нередко передают URL страницы, на которой произошёл сбой. Если в адресе есть токен, он окажется в событии мониторинга, панели аналитики или экспортированном отчёте.

Здесь браузер вообще не обязан помещать секрет в `Referer`: скрипт получает его напрямую из объекта `location`. Поэтому одной настройки заголовков недостаточно. Нужно проверить, какие поля отправляют аналитические библиотеки, SDK для ошибок, системы записи сессий и инструменты производительности.

Особенно опасны сервисы, которые сохраняют снимок страницы, DOM-дерево или пользовательские действия. Даже если URL не передаётся отдельным полем, он может попасть в запись через адрес текущего документа.

3. История браузера и синхронизация

Полный адрес сохраняется в истории браузера. Если пользователь вошёл в аккаунт браузера, история может синхронизироваться с другими устройствами.

Токен также способен появиться:

- в подсказках адресной строки;
- в списке недавно закрытых вкладок;
- в резервной копии профиля;
- в инструментах автозаполнения;
- на демонстрации экрана во время видеозвонка.

Даже одноразовый ключ восстановления может остаться доступным после завершения операции. Если устройство потеряно, заражено вредоносным ПО или используется несколькими людьми, риск возрастает.

4. Копирование и пересылка URL

Пользователь часто копирует ссылку из адресной строки и отправляет её коллеге или специалисту поддержки. Так секрет добровольно покидает контролируемую систему.

Возможны два сценария. Получатель открывает ссылку - и получает доступ к операции, для которой токен предназначался другому человеку. Либо ссылка автоматически обрабатывается промежуточным сервисом.

Мессенджеры, почтовые клиенты и корпоративные системы могут создавать предпросмотр страницы. Для этого их робот обращается по указанному адресу. Одноразовый токен при таком запросе иногда помечается использованным, после чего пользователь видит сообщение об истёкшем сроке действия.

Поэтому ссылку с секретом нельзя считать обычным текстом. Она может быть автоматически скопирована, просканирована, раскрыта в уведомлении или сохранена в истории переписки.

5. Прокси с расшифровкой TLS

HTTPS защищает запрос от наблюдения на маршруте, но не от инфраструктуры, которая сама расшифровывает трафик. В корпоративных сетях могут работать прокси с TLS-инспекцией. Они устанавливают отдельное защищённое соединение с клиентом и сервером, а содержимое запроса видят полностью.

Такой узел способен записать URL с токеном, передать его в систему фильтрации или сохранить в журнале безопасности. С точки зрения браузера соединение остаётся HTTPS, но конфиденциальность между пользователем и конечным сервером уже зависит от политики организации.

К аналогичным местам относятся балансировщики, API-шлюзы, WAF, системы трассировки и промежуточные сервисы авторизации.

Что использовать вместо токена в URL

Если есть выбор, секрет следует передавать в заголовке или теле POST-запроса. В таком случае он не попадает в адресную строку, историю браузера и `location.href`.

Например, вместо ссылки:

```text
https://example.test/reset?token=abc123
```

можно открыть нейтральную страницу, а затем отправить код отдельным запросом:

```http
POST /reset
Authorization: Bearer abc123
```

или передать его в теле POST-запроса.

Это не устраняет риск полностью. Заголовки могут записываться прокси и серверными журналами, а тело запроса - системами диагностики. Но количество каналов утечки становится значительно меньше.

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

Как снизить последствия утечки

Токен должен иметь минимальные права. Ключ восстановления пароля не должен одновременно давать доступ к API, профилю и административным операциям.

Полезно применять следующие меры:

1. Ограничивать срок действия несколькими минутами.
2. Делать токен одноразовым.
3. Не передавать в URL постоянные API-ключи и идентификаторы с широкими полномочиями.
4. После открытия ссылки перенаправлять пользователя на чистый адрес без параметров.
5. Использовать `history.replaceState`, чтобы убрать секрет из адресной строки и дальнейшей навигации.
6. Запрещать индексацию страниц с чувствительными параметрами.
7. Настраивать редактирование или маскирование query-параметров в логах.
8. Исключать URL, заголовки и содержимое форм из систем записи сессий.
9. Проверять настройки аналитики и мониторинга.
10. Устанавливать строгую политику `Referrer-Policy`, например `strict-origin-when-cross-origin` или более ограничительный вариант.

Что проверить в проекте

Аудит начинается не с браузера, а с перечня всех мест, куда может попасть URL. Нужно посмотреть конфигурацию веб-сервера, CDN, балансировщика, WAF и API-шлюза. Отдельно проверяются access-логи, error-логи и трассировка запросов.

Затем следует изучить клиентский код. Ищите использование `location.href`, `location.search`, `document.referrer`, отправку URL в аналитику и передачу адреса в сообщения об ошибках.

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

Наконец, стоит воспроизвести реальные пользовательские сценарии: копирование ссылки, открытие предпросмотра в мессенджере, переход по кнопке "назад", синхронизацию истории и работу корпоративного прокси.

Главный вывод прост: сегодня токен действительно обычно не уходит стороннему сайту в полном межсайтовом `Referer`. Но это не делает URL безопасным хранилищем секрета. Он по-прежнему виден собственному серверу, клиентскому JavaScript, журналам, аналитике, истории браузера, прокси и любому человеку, которому ссылку переслали. Поэтому секреты лучше исключать из URL архитектурно, а если это невозможно - ограничивать их срок, права и последствия компрометации.

Прокрутить вверх