Как провести пентест REST API с помощью curl

Sat Aug 15 2026

Большинство руководств по пентесту API разбивают список OWASP на одиннадцать несвязанных задач. Но такой порядок упускает самые быстрые проверки. Вторая identity и необработанный JSON обычно выявляют ошибки авторизации раньше, чем поиск payload.

И тестируйте только staging или намеренно уязвимый lab API с письменным разрешением. Вам нужны две тестовые identity с разными владельцами или ролями, действительные токены, Bash с curl и jq, а также согласованный предел количества запросов и условие остановки. Burp Suite полезен для перехвата и повторной отправки; OWASP ZAP — разумный бесплатный вариант для автоматизированного baseline-сканирования.

Начните с lab API по адресу https://api.shop.test. Фикстура предоставляет Alice заказ 1001, а Bob — заказ 1002. Используйте эквивалентные тестовые данные в своей среде. Мы обнаружим маршруты, повторно отправим объекты от имени разных identity, проверим токены и поля, измерим лимиты, протестируем бизнес-процессы и проследим пользовательские URL. По состоянию на 15 августа 2026 года OWASP API Security Top 10 остаётся редакцией 2023 года; используйте её для маркировки находок, но приоритет в порядке тестирования отдавайте границам identity и авторизации.

В этой статье

Область тестирования — это контроль, а не дисклеймер

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

Настройте harness. Переменная proxy направляет трафик командной строки через Burp или другой одобренный interceptor. Импортируйте его CA только в соответствии с одобренной настройкой вашей lab.

export HTTPS_PROXY=http://127.0.0.1:8080

BASE=https://api.shop.test
TOKEN_A='alice-test-token'
TOKEN_B='bob-test-token'

Значения ниже обозначают авторизованную цель. OWASP’s API Security Project предоставляет framework рисков; он не заменяет evidence запросов и ответов. Начните с BOLA, broken authentication, BOPLA и BFLA. Эти контроли устанавливают, кто может получить доступ к объекту, стать identity, изменить свойство или вызвать функцию.

Составьте инвентаризацию маршрутов до проверки контролей

Список маршрутов SPA — это источник для discovery, а не граница покрытия. Проверьте распространённые спецификации и metadata identity. Что раскрывает API?

for p in swagger-ui api-docs v3/api-docs openapi.json \
  .well-known/openid-configuration; do
  curl -s -o /dev/null -w "%{http_code} $p\n" "$BASE/$p"
done

Если документ OpenAPI защищён, повторите запрос с одобренным тестовым токеном. 401 означает «требуется authentication». Документация всё ещё может быть доступна.

curl -fsS "$BASE/openapi.json" |
  jq -er 'if (.paths | type) == "object" then .paths | keys[] else error("no object paths") end' \
  > endpoints.txt

Здесь -f отклоняет HTTP-ошибки, а -e заставляет команду завершиться с ошибкой при null-результате. Некорректный документ уже приводит к ошибке jq.

Для доступного staging frontend извлеките похожие на API URL из JavaScript:

katana -u https://app.shop.test -jc -silent |
  grep -E '/api|/v[0-9]' |
  sort -u > discovered-urls.txt

Храните полные URL отдельно от относительных путей OpenAPI. Инструментам вроде nuclei нужны доступные URL:

nuclei -l discovered-urls.txt -t http/exposures/

Проверьте старые версии, используя prefix, который фактически применяет ваш API:

for v in v1 v2 v3 internal beta; do
  curl -s -o /dev/null -w "%{http_code} /api/$v/users\n" \
    "$BASE/api/$v/users"
done

Успешный ответ документации помогает найти направления для проверки. Он не определяет их severity. Отдельно зафиксируйте открытые docs, устаревшие маршруты, stack traces и secrets на стороне клиента. Ограничьте production-документацию, выведите старые версии из эксплуатации, отключите traces и удалите поставленные вместе с приложением ключи.

Если инвентаризация выявляет /api/orders/{id}, начните с него.

Две identity выявляют авторизацию объектов

Повторно отправьте объект от имени неправильной identity

BOLA — самая быстрая полезная демонстрация. Сохраняйте метод, форму пути, headers и токен неизменными, меняя только ID объекта. Затем выполните проверку в обратном направлении для обеих identity.

Alice читает свой заказ:

curl -s "$BASE/api/orders/1001" \
  -H "Authorization: Bearer $TOKEN_A" |
  jq .

Теперь Alice запрашивает заказ Bob:

curl -i -s "$BASE/api/orders/1002" \
  -H "Authorization: Bearer $TOKEN_A"

403 или нераскрывающий информацию 404 могут быть корректными. Важнее ownership и возвращаемые данные, чем один только status. Ответ, содержащий заказ Bob, или изменение его состояния — это находка.

Повторите проверку в обратном направлении:

curl -i -s "$BASE/api/orders/1001" \
  -H "Authorization: Bearer $TOKEN_B"

UUID усложняют угадывание идентификаторов, но не обеспечивают authorization. Тестируйте идентификаторы в путях, query strings, JSON bodies, headers, nested resources, batch arrays, exports, downloads и в каждом поддерживаемом методе. Проверка чтения может существовать, тогда как update или delete остаётся открытым.

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

Используйте ffuf только с известными тестовыми ID и низкой частотой запросов:

ffuf -u "$BASE/api/orders/FUZZ" \
  -w <(printf '%s\n' 1001 1002) \
  -H "Authorization: Bearer $TOKEN_A" \
  -mc 200,403,404 \
  -rate 1

Зелёный результат scanner в этом случае почти бесполезен. Сохраните исходный запрос и replay. Зафиксируйте различия между ответами. Отметьте затронутую границу identity и любое изменение состояния. Это и есть evidence.

Рассматривайте токены как входные данные, а не как факты

Читаемый JWT доказывает encoding. Он ничего не говорит о проверке подписи или validation claims.

Этот decoder обрабатывает распространённое дополнение base64url:

JWT="$TOKEN_A"

python3 - "$JWT" <<'PY'
import base64, json, sys

for part in sys.argv[1].split(".")[:2]:
    part += "=" * (-len(part) % 4)
    print(json.dumps(
        json.loads(base64.urlsafe_b64decode(part)),
        indent=2
    ))
PY

Проверьте alg, kid, iss, aud, sub, роли и срок действия. Основным тестом является ручной replay claims. Для версий, поддерживающих эти режимы, jwt_tool может создавать варианты:

jwt_tool --help
jwt_tool "$JWT" -T
jwt_tool "$JWT" -X a

Сгенерированный вариант — всего лишь тестовый артефакт. Сообщайте об algorithm confusion или принятии alg:none только в том случае, если сервер принимает токен и меняет доступ.

В lab, использующей выданные для тестов токены и тестовые ключи, можно проверить слабые HMAC secrets:

jwt_tool "$JWT" -C -d /usr/share/wordlists/rockyou.txt

Для проверки ссылки на ключ используйте только одобренный источник ключа в lab:

jwt_tool "$JWT" -X k -pk ./lab-issuer-public.pem

Проверьте изменённые payload, недействительные подписи, просроченные токены, неправильные aud или iss, replay после logout и повторное использование refresh token. Само наличие kid, jku или x5u не является находкой. Проверьте, остаются ли такие ссылки ограниченными доверенными ключами, и выполняйте запросы только внутри lab.

Исправление начинается с allowlist алгоритмов, а также проверки подписи и claims. Ограничьте ссылки на ключи. Выполняйте rotation refresh tokens, отзывайте их и выявляйте повторное использование. Слабое место этого метода — асинхронное поведение: curl может показать успешную постановку в очередь, тогда как опасная работа выполняется позже. Подтвердите такие случаи с помощью логов или владельца; не делайте вывод об impact только на основании HTTP-ответа.

Сравните возвращаемые поля с доступными для записи

BOPLA охватывает чтение и запись:

НаправлениеКонкретное evidenceИсправление
Раскрытие данных при чтенииResponse, соответствующий роли, содержит поле, которое должен исключатьВозвращайте явный response DTO
Overposting при записиПоследующий GET показывает, что isAdmin, ownership или другое защищённое значение изменилосьПрименяйте server-side allowlist доступных для записи полей

Изучите полный response Alice, замаскировав secrets:

curl -s "$BASE/api/users/me" \
  -H "Authorization: Bearer $TOKEN_A" |
  jq .

Проверяйте по одному полю за раз, используя disposable data. После каждой mutation сбрасывайте lab account:

curl -i -s -X PATCH "$BASE/api/users/me" \
  -H "Authorization: Bearer $TOKEN_A" \
  -H "Content-Type: application/json" \
  -d '{"verified":true}'

Только если это разрешено тестовой средой, попробуйте защищённые поля, такие как role, isAdmin, ownerId или balance. Возвращённое в response поле — это направление для проверки. Последующее чтение, показывающее изменённые privilege или ownership, — это evidence.

Arjun может предложить JSON-параметры, хотя обработка body зависит от версии:

arjun -u "$BASE/api/users/me" \
  -m JSON \
  -H "Authorization: Bearer $TOKEN_A"

Измерьте лимиты и протестируйте identity, которой они доверяют

Зелёный результат scanner в этом случае почти бесполезен. Rate limiting — это вопрос identity и контроля затрат, а не число запросов из одного source address.

Отправьте ограниченный тест login:

for i in $(seq 1 20); do
  curl -s -o /dev/null -w "$i %{http_code}\n" \
    -X POST "$BASE/api/login" \
    -H "Content-Type: application/json" \
    -d "{\"user\":\"alice-test\",\"pass\":\"wrong-$i\"}"
  sleep 0.2
done

curl -s -D - -o /dev/null \
  -X POST "$BASE/api/login" \
  -H "Content-Type: application/json" \
  -d '{"user":"alice-test","pass":"wrong-final"}'

Если запросы 1–8 возвращают 401, а запрос 9 — 429, зафиксируйте окно из восьми попыток. Повторите после сброса и запишите Retry-After, если он присутствует.

Сохраняйте token, path, payload, timing и количество запросов неизменными, сравнивая baseline и forwarded headers:

for mode in baseline rotated; do
  printf '%s: ' "$mode"
  for i in $(seq 1 5); do
    args=()
    [ "$mode" = rotated ] &&
      args=(-H "X-Forwarded-For: 192.0.2.$i")
    curl -s -o /dev/null -w "%{http_code} " \
      "$BASE/api/search?q=test" "${args[@]}"
  done
  printf '\n'
done

Большое количество успешных запросов при rotation предполагает, что spoofable header влияет на лимит. Само по себе это не доказывает bypass.

Проверьте pagination и ресурсоёмкие маршруты с небольшими запросами:

for q in 'limit=9999999' 'per_page=100000' 'page_size=-1'; do
  curl -s "$BASE/api/items?$q" -o /dev/null \
    -w "$q status=%{http_code} bytes=%{size_download}\n"
done

Тестируйте exports, отправку OTP, batch routes, а также глубину или batching GraphQL только в том случае, если они входят в scope. Остановитесь на согласованном пределе.

Для coupon lab API без списания средств проверяйте состояние, а не нагрузку:

for i in $(seq 1 3); do
  curl -s -X POST "$BASE/api/apply-coupon" \
    -H "Authorization: Bearer $TOKEN_A" \
    -H "Content-Type: application/json" \
    -d '{"code":"SAVE50"}'
  printf '\n'
done

Первое применение должно изменить тестовую корзину. Последующие попытки должны быть отклонены или не менять её.

Отделяйте доступ к функциям от злоупотребления бизнес-процессами

Обычный token Alice не должен получать доступ к административным данным:

curl -i -s "$BASE/api/admin/users" \
  -H "Authorization: Bearer $TOKEN_A"

Тестируйте задокументированные методы, версии, bulk routes и любое поведение method-override:

curl -i -s -X POST "$BASE/api/orders/1001" \
  -H "Authorization: Bearer $TOKEN_A" \
  -H "X-HTTP-Method-Override: DELETE"

Сравните response и состояние с обычным запросом, используя disposable data. Для каждого метода и версии необходима authorization по принципу default-deny.

Затем протестируйте чувствительные flows с небольшим количеством запросов. Повторите приведённый выше запрос coupon, а если инвентаризация содержит referral или quantity controls, проверьте их с disposable accounts. Для /api/checkout используйте только товары без списания средств и небольшое согласованное количество запросов. Успешный status важен, когда он доказывает несанкционированный переход состояния. Он также может показать повторную скидку или нарушение ограничения количества.

Проследите значения до предполагаемых sink

Рассматривать injection как одну строку SQLi — ленивое тестирование. Сначала выясните, куда попадает каждое контролируемое пользователем значение, затем используйте безвредные probes и подтвердите sink.

InputПредполагаемый sinkБезопасный probeEvidence
sort или filterDatabase или search parserПереключайтесь между задокументированными значениямиReflection, ошибка parser или контролируемое изменение результата
Поле, похожее на templateTemplate rendererОтправьте уникальный инертный markerНеожиданный server-side rendering
JSON или XML fieldParser или downstream serviceДобавьте одно безвредное неизвестное полеПоведение parser или downstream
url или callback_urlServer-side HTTP clientОтправьте одобренный callback URLMetadata out-of-band запроса

Endpoint lab /api/fetch принимает URL:

curl -i -s -X POST "$BASE/api/fetch" \
  -H "Authorization: Bearer $TOKEN_A" \
  -H "Content-Type: application/json" \
  -d '{"url":"<YOUR_CONTROLLED_CALLBACK_URL>"}'

Blind fetch может не вернуть полезное body; evidence предоставляет callback observer. Зафиксируйте timestamp, source address, method и path.

Затем протестируйте контролируемый internal service:

curl -i -s -X POST "$BASE/api/fetch" \
  -H "Authorization: Bearer $TOKEN_A" \
  -H "Content-Type: application/json" \
  -d '{"url":"http://127.0.0.1:8080/health"}'

Response от lab service убедительно свидетельствует, что fetch path достиг его. Подтвердите origin с помощью service logs или callback data. Используйте альтернативные представления loopback только в контролируемой staging network и никогда не обращайтесь к production internal addresses. Не запрашивайте cloud metadata credentials; для подтверждения SSRF path достаточно доказать контролируемую доступность internal service.

Если инвентаризация содержит source_url, link, image_url или webhook, примените тот же процесс. Для гипотез SQL, NoSQL, command, template и parser фиксируйте reflection, errors, timing и downstream behavior. Не заявляйте о наличии sink только на основании странной ошибки.

Отчёт должен содержать запрос, ответ и изменение состояния

Чек-лист находки

Используйте labels редакции 2023 года. BOPLA — это A03, unrestricted resource consumption — A04, sensitive business flows — A06, а SSRF — A07. Классификация OWASP сама по себе не определяет severity.

Перед завершением assessment проверьте:

  • endpoint и method
  • использованные identity и role
  • исходный и повторно отправленный request
  • response и существенное различие в JSON
  • затронутые data или state
  • OWASP ID и remediation
  • условие retest

Чек-лист обеспечивает покрытие. Тройка request/response/state предоставляет вам finding.