Ручка приёма ошибок с фронта пишет прямо вам в мессенджер
Приёмник JS-ошибок обычно открыт всем: ни токена, ни сессии. Разбираем, во что это превращается, когда с него идут оповещения, и чем закрыть, не ослепив себя.
Почти на каждом сайте, где следят за фронтом, есть точка вроде
/api/log_error.
Браузер посетителя присылает туда упавший скрипт: сообщение, стек, адрес страницы.
Без неё падения фронта не видны вообще, в серверных логах их нет, и человек просто
уходит.
У этой точки есть свойство, которое отличает её от всех остальных: она обязана работать до всякой сессии и без токена. Ошибка часто ломает ровно тот код, который токен получает. Потребуйте токен и вы перестанете принимать именно те падения, ради которых точка сделана.
Дальше начинается интересное. Принятое сообщение мало кто просто пишет в файл: обычно рядом стоит оповещение, чтобы про поломку узнать сразу. И вот тут открытая точка перестаёт быть журналом и становится каналом. Прямым каналом от любого человека в интернете до вашего личного мессенджера.
Что мы нашли у себя
Проверка своей же ручки заняла один запрос. Ниже настоящий обмен, только адрес сайта убран:
POST /api/log_error.php
Origin: https://example.com
{"message":"ПРОБА БЕЗОПАСНОСТИ: текст, пришедший с чужой страницы"}
→ 200 {"ok":true}
Принято. Текст лёг в журнал сайта, а оттуда ушёл в очередь оповещений со статусом «активная тревога». Доставки в мессенджер не случилось по единственной причине: наш нотификатор ждёт повтора одного и того же сообщения, чтобы не шуметь на разовых сбоях. То есть от чужого текста в личном канале нас отделял один повторный запрос, а не защита.
Отдельно стоит осознать, что именно в такой канал можно послать. Не только шум. Сообщение об ошибке — это строка, которую владелец читает внимательно и в спешке, обычно с телефона. Строка, оформленная как ошибка платежа или как предупреждение хостинга, читается как своя. Открытая точка приёма ошибок это не «мусор в логах», это доверенный канал, отданный наружу.
Почему обычные способы защиты тут не работают
Токен. Разобрали выше: он выключает точку тогда, когда она нужнее всего.
Лимит на адрес. У нас стоял, двадцать сообщений с одного адреса в час. Он спасает от одного заклинившего браузера и совсем не спасает от человека, который прислал двадцать разных текстов. А ещё файл счёта читается и перезаписывается на каждый запрос: внутри часа он растёт по записи на каждый новый адрес, и поток с многих адресов раздувает его без предела. Мы добавили потолок на число адресов, потому что счётчик, который сам становится нагрузкой, защищает плохо.
Дедупликация оповещений. Именно она нас и выручила, но защитой она не является: это глушитель шума. Он держится на том, что тексты повторяются, а злоупотребление как раз тексты и разнообразит.
Чем закрыли
Требованием, чтобы запрос пришёл со своей страницы: свой
Origin
или свой
Referer.
Нет ни одного заголовка, отказ.
Это не криптографическая граница, и делать вид, что она такая, нечестно: любой клиент, кроме браузера, подставит нужный заголовок в одну строку. Но задача другая. Мы убираем тривиальное злоупотребление: чужую страницу со вставленной ручкой, скрипт, робота, случайный обход. После этого, чтобы писать нам в канал, нужно действовать намеренно, а намеренную атаку останавливают не на этом уровне.
Ловушка, на которой мы почти ослепли
Первая мысль была принимать только по
Origin:
заголовок для этого и придуман. Проверили живым браузером и обнаружили, что
однодоменный запрос из кода страницы не посылает
Origin
вовсе, он проходит по
Referer.
Прими мы только первый заголовок, часть своих же падений перестала бы
доезжать. Узнали бы мы об этом по тишине в журнале, то есть никогда: тишина
выглядит как «ошибок нет».
Что проверить у себя
Ниже семь пунктов, по которым мы прошли сами. Отмечайте те, что у вас закрыты: инструмент считает в браузере и ничего не отправляет.
Проверьте свою ручку по семи пунктам
Отмечайте закрытое. Считает браузер, отмеченное никуда не отправляется, кода отправки на этой странице нет.
Читать дальше
-
Ответ 200, а внутри ошибка: как это ломает интеграции
Проверка «status === 200» пропускает отказы. У нас на этом стояли два сайта и один виджет, и в журнале был ложный диагноз.
-
17 ошибок, за которые я заплатил сам
Модный стек, преждевременное масштабирование, доводка до запуска, отсутствие мониторинга. Каждый пункт оплачен, у каждого названа цена.
-
Зелёный набор тестов ничего не обещает
Все проверки ручки били в отказы: нет токена, чужой источник, слишком длинный вход. Удачный путь не проверял никто, и дефект уехал на бой при зелёном наборе.
Если у вас есть публичная точка приёма чего угодно с фронта, самый быстрый способ узнать её состояние это один запрос с чужим источником. Мы делаем такую ревизию на аудите: смотрим все точки, доступные без токена, и куда попадает принятое от них.