В логе доступа чтения заказов v1 идут по чужим владельцам, а v2 защищён — найдите и исправьте дефект
Партнёрская интеграция всё ещё вызывает устаревший API заказов v1. Ниже таблица маршрутов шлюза и фрагмент лога доступа одной аутентифицированной сессии.
Ограничения:
v1иv2обслуживает один и тот же сервис заказов, загружающий заказ только по id- правило шлюза
owner-checkсверяет субъекта токена с владельцем заказа - заказы 50113, 50114 и 50115 принадлежат трём разным клиентам
- контракт с партнёром не допускает поломки поведения
v2
gateway routes:
GET /api/v2/orders/{id} -> order-service filters: auth, owner-check
GET /api/v1/orders/{id} -> order-service filters: auth
access log (token subject = customer 8842):
09:12:04 GET /api/v2/orders/50113 403
09:12:19 GET /api/v1/orders/50113 200 1.4kB
09:12:22 GET /api/v1/orders/50114 200 1.5kB
09:12:25 GET /api/v1/orders/50115 200 1.4kB
Найдите и исправьте уязвимость.
Проверка владельца существует только как фильтр шлюза на v2, поэтому устаревший маршрут v1 доходит до того же сервиса и отдаёт любой заказ по id. Перенесите проверку в сам сервис заказов и выведите v1 из эксплуатации.
- ✗Считать фильтр шлюза средством авторизации для всех маршрутов за ним
- ✗Забывать, что устаревшая версия по-прежнему доходит до текущего сервиса
- ✗Чинить формат идентификатора или код статуса вместо отсутствующей проверки
- →Как найти все маршруты, ещё доступные, но отсутствующие в текущей спецификации?
- →Какой алерт по этому логу выявил бы схему в первый же день?
Фильтр owner-check навешан лишь на правило v2, а v1 идёт к тому же сервису только через auth. Сервис загружает заказ по одному id, поэтому аутентифицированный клиент 8842 читает заказы трёх разных владельцев — это нарушение авторизации на уровне объектов (BOLA), а устаревшая версия просто обходит единственное место, где проверка была.
Правильное исправление — перенести проверку владельца в сам сервис заказов, чтобы её проходила любая версия и любой новый маршрут, а затем убрать v1 из таблицы маршрутов. Дублировать фильтр на шлюзе недостаточно: следующая версия или внутренний вызов снова пройдут мимо.
async function getOrderForCaller(orderId, callerId) {
const order = await orders.findByIdForOwner(orderId, callerId);
if (!order) throw new NotFound(); // чужой и несуществующий заказ неотличимы
return order;
}
Отдельно стоит завести алерт на серию ответов 200 по разным владельцам в одной сессии и сверять таблицу маршрутов с актуальной спецификацией, чтобы забытые версии не жили годами.