Авторские курсы Михаила Тарасова

Уязвимости в Signed URLs (AWS/GCS/Azure) — неверная валидация экспирации → вечные ссылки

Уязвимости в Signed URLs (AWS/GCS/Azure) — неверная валидация экспирации → вечные ссылки.

Что это за хрень
Signed URLs — это временные ссылки на приватные объекты в облачных хранилищах (S3, GCS, Azure Blob). Идея простая: генерируешь URL с подписью HMAC, добавляешь expiration (срок годности), и юзер может скачать файл без аутентификации. Через час (или сколько там поставил) — ссылка протухает. В теории.

На практике — разработчики косячат на каждом шагу, и ты получаешь вечный доступ к банковским выпискам/паспортам/интимным фоточкам. Welcome to bug bounty paradise.

Где бажат (топ-3 классики)
1. Валидация только на клиенте
Бэкенд генерит signed URL с Expires=3600, отдаёт фронту. Фронт показывает таймер “Ссылка истечёт через 59:59…”. Таймер закончился — UI блокирует кнопку. Но сама ссылка всё ещё жива, потому что сервак не перепроверяет.
Пруф:

Если возвращает 200 OK — поздравляю, ты нашёл вечную ссылку.
2. Слепое доверие параметру Expires
AWS S3 принимает параметр Expires в Unix-timestamp. Некоторые самописные обёртки (типа Ruby Fog, старые версии boto2) не валидируют его перед подписью. Результат:

Атакующий засовывает user_expires=9999999999 (год 2286) — получает ссылку на 261 год вперёд.
Эксплойт:

3. Утечка подписывающих ключей
Azure Blob использует Shared Access Signature (SAS) с секретным ключом. Если ключ засветился (например, в гит-репе или через SSRF на metadata-endpoint), ты можешь крафтить свои вечные SAS-токены:

Че почем: атакуем на практике
AWS S3 — Fuzzing параметров
Инструмент: s3scanner + собственные скрипты.

Если видишь <Error><Code>SignatureDoesNotMatch</Code> — подпись проверяется. Но если 200 OK через месяц — бинго.

Google Cloud Storage — Time Travel
GCS использует Expires в формате Unix timestamp. Баг: некоторые либы игнорируют Expires, если есть X-Goog-Date в будущем.

Если GCS не проверяет заголовок против Expires — файл твой.

Azure Blob — SAS Token Replay
Azure SAS токены содержат se=2025-11-10T17:17:00Z (expiry). Но если backend генерит их через устаревший SDK (Azure.Storage.Blobs < 12.8.0), валидация пропускается при определённых комбинациях параметров.
Эксплойт:

Автоматизация охоты
Nuclei Template

Python-скрипт для массовой проверки

Доказательство для баунти
1. Скриншот оригинального Expires (например, Expires=1699650000 → 10 ноября 2025)
2. Модифицированный запрос с Expires=9999999999
3. Ответ 200 OK с содержимым файла через месяц после генерации
4. Impact: Доступ к PII/финансам/медицинским данным навсегда
Типичный payout: $500-$5000 в зависимости от критичности данных.

Советы
Если не зашло — копай глубже:
1. Проверь Cache-Control: Если CDN кеширует signed URL с max-age=31536000, файл доступен даже после протухания подписи. Тест: curl -H "If-Modified-Since: Thu, 01 Jan 1970 00:00:00 GMT" ...
2. SSRF на metadata: Облачные инстансы хранят IAM-роли/ключи по адресу http://169.254.169.254/latest/meta-data/iam/security-credentials/. Если есть SSRF — дампишь креды, генерируешь свои вечные токены.
3. Race condition в refresh: Некоторые API обновляют signed URL через /refresh-link. Засыпай endpoint’у while true; do curl /refresh-link & done — может, сервак выдаст токен с Expires=0.
4. Логирование токенов: Ищи в Sentry/Datadog/CloudWatch логи с полными signed URLs. Часто разрабы логируют их «для дебага» — и забывают стереть.
5. Fallback на публичный режим: Если валидация падает с ошибкой, некоторые либы открывают доступ вместо 403. Тест: curl --max-time 0.01 "signed-url" (таймаут → ошибка валидации → fallback).

Мои курсы