Bug2Lab · Access Control · IDOR + Low Entropy IDs

Прикрепление файла к чужому приватному отчету / чату

Любой авторизованный пользователь может загрузить файл и «приклеить» его к любому чужому репорту, в том числе приватному или во внутреннем чате. Интерфейс будет показывать этот файл как будто его загрузил владелец репорта, не злоумышленник. Это удар по доверию и приватности.

1
Как выглядит атака
Злоумышленник:
1. Логинится как обычный пользователь.
2. Делает запрос «загрузи файл».
3. Указывает ID чужого репорта/комментария.

Файл появляется в приватном репорте жертвы так, будто это она его приложила.
POST /api/bug-bounty/bizone-files/ HTTP/1.1 Authorization: Basic ... Content-Type: multipart/form-data; boundary=...
--boundary Content-Disposition: form-data; name="file"; filename="THIS_PLATFORM_HACKED.jpg" Content-Type: image/jpeg [binary data] --boundary-- HTTP/1.1 201 Created { "id":530, "name":"THIS_PLATFORM_HACKED.jpg", "file":"https://.../THIS_PLATFORM_HACKED.jpg?...", "permissionRules":{ "uploadImage":true, "add":true, "view":true, "list":true } }
2
Почему это стало возможным
Сервер не проверяет, что репорт принадлежит текущему пользователю. Он просто верит переданным ID, а ID короткие и легко перебираются.
Проблемный код (упрощённо)
20@PostMapping("/attachFile")
21public ResponseEntity<?> attach(@RequestParam("reportId") Long reportId,
22 @RequestParam("fileId") Long fileId,
23 Principal me){
24 Report r = reportRepo.findById(reportId); // ❌ без проверки владельца
25 File f = fileRepo.findById(fileId); // ❌ без проверки кто загрузил
26 r.attach(f); // ✅ для кода, ❌ для безопасности
27 reportRepo.save(r);
28 return ResponseEntity.ok("ok");
29}
3
Как это чинится правильно
1) Проверяем, что текущий пользователь — владелец репорта И владелец файла.
2) Не позволяем указывать чужие ID напрямую.
3) Логируем, кто реально прикрепил файл, и показываем это в UI.
Исправленный паттерн
20@PostMapping("/attachFile")
21public ResponseEntity<?> attach(@RequestParam("reportId") Long reportId,
22 @RequestParam("fileId") Long fileId,
23 Principal me){
24 Report r = reportRepo.findById(reportId);
25 if(!r.getAuthorId().equals(me.getName())){
26 return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); // ✅ проверка владельца
27 }
28 File f = fileRepo.findById(fileId);
29 if(!f.getUploaderId().equals(me.getName())){
30 return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); // ✅ нельзя прикрепить чужой файл
31 }
32 r.attachWithAudit(f, me.getName(), Instant.now()); // audit trail
33 reportRepo.save(r);
34 return ResponseEntity.ok("ok");
35}

bug2regress Регрессионный тест (стоп-кран)

Тест пытается: 1) залогиниться как обычный юзер А, 2) создать файл от имени А, 3) прикрепить этот файл к репорту B (который не принадлежит А). Если API вернуло 200 — билд стопаем. Мы ждём 403.

#!/bin/bash
# attach-hijack-check.sh

# 1. создаём тестовый файл (как user A)
FILE_ID=$(curl -s -X POST "https://staging.example.internal/api/files" \
  -H "Authorization: Bearer USER_A_TOKEN" \
  -F "file=@poc.jpg" | jq -r '.id')

# 2. пробуем прилепить файл к чужому репорту REPORT_B
STATUS=$(curl -s -o /tmp/resp.txt -w "%{http_code}" \
  -X POST "https://staging.example.internal/api/attachFile" \
  -H "Authorization: Bearer USER_A_TOKEN" \
  -F "reportId=REPORT_B" \
  -F "fileId=$FILE_ID")

if [ "$STATUS" = "200" ]; then
  echo "[BLOCK] пользователь А может прикреплять файлы к репорту B"
  exit 1
fi

echo "[OK] нельзя прилепить файл к чужому приватному репорту ($STATUS)"
exit 0

Этот тест фиксирует бизнес-обещание: «файлы во внутреннем чате и приватных репортах принадлежат только авторам, и никто не сможет незаметно подложить что-то от их имени».

Проверка понимания Где реальный риск?

Стандарт: нельзя полагаться на "раз он авторизован — значит ему можно всё". Каждый объект проверяем на владение или явное право.

Теория (по желанию) Почему это не просто баг, а репутационная бомба