Перейти к основному содержанию
Блог

Веб

Безопасность API: практическое руководство по OWASP API Security Top 10

API за мобильным приложением и SPA на экспертизе проверяют отдельно. Каждый пункт OWASP API Top 10 (2023), как проверить его самостоятельно и как закрыть в Laravel и Node.

· 11 мин. чтения

Фото: Syced, CC0 · Wikimedia Commons

Современный сайт редко бывает «просто фронтендом». React- или Vue-приложение в браузере, мобильное приложение, системы партнёров — все общаются с одним API. Пользователь видит в интерфейсе только разрешённые ему кнопки, но для прямого запроса к API кнопки не нужны: хватит curl или прокси-инструмента. Поэтому безопасность API должна опираться не на ограничения интерфейса, а на проверки на сервере.

У OWASP для этой области есть отдельный список — OWASP API Security Top 10 (2023). В этой статье мы коротко объясняем каждый пункт, показываем, как проверить его в своей системе и как исправить на примере Laravel и Node (Express).

Почему это важно для экспертизы

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

На экспертизе нашего собственного продукта — SaaS-платформы для налогов и финансов — часть находок относилась именно к этому классу: действия, которые можно было выполнить без нужной проверки состояния (ошибка бизнес-логики), и лишние данные в ответах (раскрытие информации).

API1: Broken Object Level Authorization (BOLA)

Первая и самая распространённая проблема. API возвращает объект по идентификатору, но не проверяет, принадлежит ли он пользователю, отправившему запрос. В веб-приложениях это известно как IDOR — подробно мы писали об этом в отдельной статье.

Проверка: заведите два тестовых аккаунта (A и B). Получите объект аккаунтом A, затем запросите тот же ID с токеном B:

curl -s https://api.your-domain.uz/v1/invoices/1042 \
  -H "Authorization: Bearer <B_TOKEN>"

Ответ должен быть 403 или 404, а не данные A. Проверяйте не только GET, но и PUT, PATCH, DELETE и эндпоинты скачивания файлов.

Исправление в Laravel — policy для каждого объекта или ограничение запроса по владельцу:

public function show(Invoice $invoice)
{
    $this->authorize('view', $invoice);
    return new InvoiceResource($invoice);
}

// или ограничить сам запрос
$invoice = $request->user()->invoices()->findOrFail($id);

В Node/Express тот же принцип: ищите не по ID, а по «ID + владелец».

const invoice = await db.invoice.findFirst({
  where: { id: req.params.id, ownerId: req.user.id },
});
if (!invoice) return res.sendStatus(404);

API2: Broken Authentication

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

Вопросы для проверки:

  • Ограничено ли число попыток на /login, /password/reset, /otp/verify?
  • Есть ли у токена срок жизни и действительно ли он аннулируется после выхода?
  • Нет ли в мобильном API незащищённого пути входа, оставленного «для старой версии»?

API3: Broken Object Property Level Authorization

Этот пункт объединяет две старые проблемы: избыточный возврат данных и mass assignment. В первом случае API возвращает весь объект модели, а нужные поля выбирает фронтенд — при этом в ответе остаются хеш пароля, внутренние заметки или телефоны других пользователей. Во втором API записывает пришедший JSON прямо в модель, и пользователь может добавить "role": "admin" или "balance": 999999.

Проверка: посмотрите ответы в DevTools или в прокси — есть ли поля, которых не видно в интерфейсе? Добавьте лишнее поле в запрос обновления профиля:

curl -s -X PATCH https://api.your-domain.uz/v1/profile \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Test","is_admin":true,"role":"admin"}'

Затем прочитайте профиль снова: изменилась ли роль?

Исправление в Laravel — API Resource для ответа и провалидированные данные на входе:

// записываются только разрешённые поля
$user->update($request->validated());

class UserResource extends JsonResource
{
    public function toArray($request): array
    {
        return ['id' => $this->id, 'name' => $this->name];
    }
}

Не используйте в модели $guarded = [], явно перечисляйте $fillable. В Node тоже не передавайте req.body напрямую в update(): выделяйте нужные поля схемой (zod, joi).

API4: Unrestricted Resource Consumption

Не ограничено, сколько ресурсов API тратит на один запрос: списки без пагинации, ?limit=1000000, загрузка огромных файлов, неограниченная отправка SMS или писем. В итоге сервер тормозит, а счета за платные сервисы (SMS-шлюз) растут.

В Laravel — middleware throttle и предел пагинации:

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

$perPage = min((int) $request->query('per_page', 20), 100);

В Express — пакет express-rate-limit и ограничение размера тела, например express.json({ limit: '100kb' }). На уровне веб-сервера можно задать client_max_body_size и limit_req — это показано в статье про nginx.

API5: Broken Function Level Authorization

Если BOLA касается объекта, то этот пункт — функции: обычный пользователь может вызвать админский эндпоинт. Часто админские пути только скрыты в интерфейсе, а на сервере роль не проверяется.

Проверка: поищите в JavaScript-файлах фронтенда или в мобильном приложении пути вроде /admin, /internal, /manage и вызовите их с токеном обычного пользователя. Там, где разрешён GET, попробуйте и DELETE.

В Laravel сгруппируйте админские маршруты и защитите их middleware или Gate:

Route::middleware(['auth:sanctum', 'can:admin'])
    ->prefix('admin')
    ->group(function () { /* ... */ });

API6: Unrestricted Access to Sensitive Business Flows

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

Близкая проблема встретилась и на нашей экспертизе: страница «успешной оплаты» показывалась до того, как платёжный провайдер подтвердил платёж. Об этом есть отдельная статья. Главное правило: принимайте смену состояния (оплачено, подтверждено, отправлено) только после проверки на сервере.

API7: Server Side Request Forgery (SSRF)

Если API отправляет запрос по URL от пользователя (импорт картинки, webhook, превью ссылки), атакующий может через сервер обратиться во внутреннюю сеть или к сервису метаданных облака. Защита: список разрешённых доменов, блокировка внутренних диапазонов IP, ограничение редиректов.

API8: Security Misconfiguration

Неверные настройки: режим отладки в production, ошибки со stack trace, лишние HTTP-методы, отсутствие заголовков безопасности, неправильный CORS. Обо всём этом у нас есть отдельные статьи: режим отладки, заголовки безопасности, CORS.

Быстрая проверка — запрос к несуществующему эндпоинту и с некорректным JSON:

curl -s https://api.your-domain.uz/v1/nonexistent
curl -s -X POST https://api.your-domain.uz/v1/login \
  -H "Content-Type: application/json" -d '{bad json'

В ответе не должно быть путей к файлам, SQL-запросов или версии фреймворка.

API9: Improper Inventory Management

Знаете ли вы, какие API у вас работают? Часто v1 обновляют, а v0 оставляют для старых мобильных приложений и забывают. Или поддомен staging.api открыт с боевыми данными. Экспертиза может найти такие «забытые» двери.

В нашем опыте была близкая находка: в браузер отдавался полный список маршрутов приложения. Это даёт атакующему готовую карту API. Если в Laravel вы используете Ziggy, выводите только нужные маршруты:

// config/ziggy.php
return [
    'only' => ['dashboard', 'invoices.*', 'profile.*'],
];

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

API10: Unsafe Consumption of APIs

Ваш API получает данные от других API (платёжная система, госсервисы, карты). Если слепо доверять внешнему ответу — записывать в базу или выводить на странице без проверки, — проблема внешней системы становится вашей. Валидируйте и внешние ответы, подключайтесь только по HTTPS, задавайте таймаут и не следуйте редиректам автоматически.

В HTTP-клиенте Laravel:

$response = Http::timeout(10)
    ->withOptions(['allow_redirects' => false])
    ->get('https://partner.example/api/status');

$data = validator($response->json(), [
    'status' => 'required|in:paid,pending,failed',
])->validate();

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

  1. Составьте список всех эндпоинтов API (php artisan route:list --path=api или роутеры Express).
  2. Подготовьте два тестовых аккаунта и один админский.
  3. Вызовите каждый эндпоинт без токена, с токеном другого пользователя и админские функции — с токеном обычного пользователя.
  4. Отправьте лишние поля в эндпоинты записи.
  5. Сравните ответы с тем, что видно в интерфейсе.
  6. Проверьте ограничение частоты на входе, OTP и SMS.
  7. Запишите результаты в таблицу и после исправления каждой находки проверьте снова.

Делайте это только в своей системе и на тестовых данных.

Граница безопасности в API — это сервер. Кнопка, скрытая в интерфейсе, защитой не является.

Чек-лист

  • В каждом эндпоинте объекта есть проверка владения (policy или ограничение запроса).
  • Админские и внутренние функции защищены на сервере по роли.
  • Ответы отдаются через API Resource или DTO, только с нужными полями.
  • В запросах на запись принимаются только провалидированные поля.
  • На входе, OTP, SMS и тяжёлых эндпоинтах есть ограничение частоты и предел пагинации.
  • Отладка выключена, ошибки возвращаются общим сообщением.
  • Старые версии API, тестовые хосты и открытая документация инвентаризированы.
  • Ответы внешних API валидируются, задан таймаут.

Итог

OWASP API Top 10 — не теория, а список конкретных вопросов, которые проверяют на экспертизе. Большинство из них сводится к одному правилу: на каждый запрос сервер должен ответить на вопрос «вправе ли этот пользователь выполнить это действие с этим объектом и в таком объёме?».

Хотите проверить свой API по этому списку до экспертизы? Оставьте заявку на сканирование — Claude AI агент быстро просмотрит эндпоинты и код, а наш специалист подтвердит находки и поможет закрыть дыры.