Платный доступ открывался одной cookie: как я нашёл это в своём сервисе

Открыл SEOSimple в другом браузере, руками поменял cookie — и увидел платный кабинет. Разбираю, где ошибся, чем это грозило и что теперь проверяю в каждом своём сервисе.

Открыл я как-то свой же SEOSimple в другом браузере, поставил руками в cookie параметр вида `auth=1` — и попал туда, куда без оплаты попадать было нельзя. Не через страшный терминал с зелёными буквами, а через обычную проверку «а что будет, если». Сидел и думал: сколько людей уже это сделали, пожали плечами и молчат.

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

Где я перепутал вход и право

Ошибка была не в самой единице. `auth=1` — это всего лишь значение. Проблема была в том, что право на платную операцию определялось значением из браузера, а не записью на сервере.

Я смешал две разные вещи: «я вошёл» и «мне можно». Пользователь может быть авторизован, но не иметь оплаченного доступа. Может оплатить одну функцию, но не иметь другую. Может потерять право после окончания периода, отмены, возврата, ручной блокировки или изменения тарифа.

А у меня логика в одном месте вела себя проще табуретки: если при входе увидели нужный признак, значит дальше можно. Проверка прав на входе — это не проверка прав. Это проверка настроения на момент входа. Через пять минут оно уже может не совпадать с реальностью.

Вот как это выглядело по смыслу:

БылоСтало
Cookie сообщала, что доступ естьCookie только связывает пользователя с сессией
Право читалось из браузераПраво читается с сервера
Проверка шла при входеПроверка идёт при каждой платной операции
Изменённое значение могло открыть кабинетИзменённое значение ничего не даёт
Ошибка пряталась в удобствеЛогика стала скучнее, зато безопаснее

Скучнее — хорошее слово для безопасности. Если код прав доступа выглядит слишком изящно, я теперь начинаю щуриться.

Чем это грозило на практике

Чем это грозило на практике

В моём случае страшилка была не в том, что кто-то увидит красивую кнопку. Кнопки денег не стоят. Деньги стоят действия за кнопками.

Если бы кто-то тихо делал пару десятков чужих прогонов в день, я бы получил три проблемы сразу

— Олег Ветров, автор блога

Каждая проверка в сервисе — это платный запрос. Где-то уходит запрос к выдаче, где-то к Wordstat, где-то к ссылочному профилю, где-то к ответам нейросетей. Пользователь нажал «проверить» — а за сценой поехала цепочка обращений, лимитов и списаний. Не банковская драма, но бухгалтерия начинает хмуриться.

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

Первая — прямые расходы. Запросы не становятся бесплатными от того, что их вызвал человек с нарисованным доступом.

Вторая — мусор в лимитах клиентов. Если лимит общий или завязан на очередь, чужие проверки начинают толкаться локтями с нормальными задачами. Потом сидишь и объясняешь, почему отчёт едет медленнее. Объяснение «я сам себе подложил грабли» обычно не украшает переписку.

Третья — грязные данные. Внутренняя статистика начинает показывать активность, которой не должно быть. Смотришь на использование функций, строишь планы, а часть цифр — это дым из твоей же щели в правах. Для SEO-сервиса это особенно неприятно: мы и так живём среди шумных данных, не хватало ещё своих.

Как я нашёл ошибку

Не сканером. Не ночным пентестом. Не потому что мне приснился протокол HTTP и сказал: «Олег, встань».

Я просто проверял крайние случаи. Есть привычка: открыть страницу в другом браузере, выйти, зайти, поменять состояние, посмотреть, где интерфейс ведёт себя слишком доверчиво. В тот раз вопрос был совсем простой: а что если открыть страницу с параметром в адресе и посмотреть, что будет дальше?

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

Я смотрел, где создаётся сессия, какие значения попадают в cookie, где сервис решает «этому пользователю можно», а где просто верит старому признаку. Ошибка нашлась не в одной магической строке, а в связке решений. Сначала удобно передали состояние на клиент. Потом удобно прочитали его обратно. Потом удобно не стали спрашивать сервер ещё раз.

Удобство, как обычно, пришло в тапочках и вынесло кассу.

Что я поменял

Починка была не про то, чтобы заменить `1` на что-то более хитрое. Это было бы косметикой. Проблема не в угадываемом значении, а в доверии к значению, которое пользователь может изменить.

Я сделал четыре вещи:

  1. Сессия стала подписанной. Cookie больше нельзя осмысленно подменить так, чтобы сервер принял её за свою. Она не сообщает права, а только помогает связать запрос с серверной сессией.
  1. Права живут только на сервере. Оплата, тариф, доступ к операциям и ограничения проверяются по серверным данным. Браузер может показывать интерфейс, но не решает, кому можно запускать платную проверку.
  1. Проверка идёт при каждой операции. Не один раз при входе, не при открытии кабинета, не «мы же уже видели». Перед платным запросом сервис снова спрашивает: есть ли право именно сейчас и именно на это действие.
  1. Добавлены логи и лимиты. Мне нужно видеть, кто запускает операции, сколько раз, с какими результатами и где начинаются странности. Лимит — не замена проверке прав, но хороший предохранитель, когда что-то всё-таки поехало не туда.

После правки интерфейс стал менее самоуверенным. Это хорошо. Интерфейс может быть удобным, но право на действие должен выдавать сервер. Каждый раз.

Что проверить у себя за вечер

Этот класс ошибки старый и живёт не только в моём сервисе. Схема обычно такая: право проверяется на клиенте, на входе, один раз, по значению, которое пользователь может изменить. Особенно часто такое вылезает в публичных сканерах, бесплатных проверках, формах и демо-доступах.

В 2026 году к обычным любопытным людям добавились ИИ-агенты. Они уже сами ходят по сайтам и формам, кликают, пробуют варианты, заполняют поля. Я отдельно писал, как готовить формы и каталоги к таким обходам: /blog/ii-agenty-hodyat-po-sajtam-kak-podgotovit-formy-i-katalog. Чем больше автоматических посетителей, тем меньше надежды на «никто не догадается».

За вечер можно пройтись по своему сервису вот так:

Что проверитьЧто должно быть
Cookie`HttpOnly`, `Secure`, `SameSite`; cookie не хранит право на платную функцию
ПраваПроверяются на сервере, а не в JS, шаблоне или параметре браузера
Платные операцииПеред каждым запуском заново проверяется доступ
ИдемпотентностьПовтор запроса не списывает лишнее и не запускает дубль без причины
ЛимитыЕсть ограничения на пользователя, сессию, IP или тип операции
Журнал операцийВидно, кто, когда и что запускал; странные серии можно найти
Отзыв сессийМожно принудительно разлогинить пользователя и закрыть старый доступ
Тест на подменуМеняешь значение на своей стороне и смотришь, верит ли ему сервер

Последний пункт звучит почти детски. Но он и ловит много взрослой глупости. Если система ломается от того, что пользователь поменял значение у себя в браузере, ломается не браузер.

Почему я пишу об этом

Почему я пишу об этом

У меня в блоге уже 123 статьи и словарь «Слоеводство» на 144 термина. Там хватает SEO, разработки, автоматизации и моих рабочих шишек. Эта — из той же коробки.

Скрытая уязвимость в своём продукте — не приговор и не повод драматично уходить в закат. Это нормальная часть работы с живым сервисом. Код растёт, функции цепляются друг за друга, старые решения переживают свой срок годности. Иногда они начинают пахнуть.

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

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

Границы этого разбора

Это не пентест и не инструкция по взлому чужих сервисов. Я описываю один класс ошибок на своём примере: доверие к клиентскому значению при проверке платных прав.

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

Но начать можно без героизма. Откройте свой сервис в другом браузере. Выйдите. Зайдите. Поменяйте состояние. Проверьте платную операцию. Посмотрите в код, где решается «можно».

Я сегодня ещё раз открыл логи и поставил напоминание проверить лимиты.

Читать дальше

Практика 25 октября 2026 Как устроена Nasloy CRM: клиентский кабинет, задачи, позиции и заявки в одном месте Своя CRM под продвижение сайтов: клиентский портал с задачами и заявками, позиции Яндекса и Google, прогноз заявок до старта, ИИ-ассистент и отчёты без ожидания. Гайды 28 октября 2026 ИИ-агенты начали сами ходить по сайтам: как подготовить формы, кнопки и каталог к визиту не-человека Осенью OpenAI и Anthropic выпустили агентов с собственным браузером: они сами открывают сайты, заполняют формы и сравнивают предложения. Разбираю, что агент не сможет сделать на типичном сайте и как это исправить, пока конкуренты не успели. SEO-инструменты 31 октября 2026 278 страниц и 9 островов: как я искал сирот на своём сайте Я прогнал nasloy.ru скриптом и посчитал, сколько ссылок ведёт на каждую страницу из текста, а не из меню. Из 278 страниц девять оказались островами. Рассказываю, где они нашлись и почему чинить их надо абзацем.

Проверка

Посмотреть, где теряется трафик

SEO-аудит страницы: 28 автоматических проверок — от индексации до микроразметки. Без регистрации, список приоритетов сразу.