< Powrót do Artykułów
◆ Case study · IDOR / BOLA

Jedno konto kandydata.
Dostęp do cudzych CV.

Techniczne, zanonimizowane case study z testów penetracyjnych systemu rekrutacyjnego: macierz uprawnień, IDOR/BOLA, request/response i naprawa klasy błędów, a nie jednego endpointu.

TRYB Black-box + konta testoweKLASA IDOR / BOLASTANDARD OWASP API Top 10WPŁYW CV i dane osobowe
IDOR
Broken Object
Level Authorization
2 konta
wystarczyły do
pełnego PoC
200 OK
tam, gdzie powinno
być 403/404
CRIT
RODO, reputacja,
utrata zaufania

Nie musisz przejąć admina, żeby zrobić incydent

W systemach rekrutacyjnych najcenniejsze dane często nie siedzą w panelu administratora, tylko w zwykłych obiektach użytkownika: CV, załącznikach, notatkach, zgłoszeniach i statusach procesu. Jeżeli backend przyjmuje identyfikator dokumentu i nie sprawdza właściciela, zwykłe konto kandydata może stać się narzędziem do masowego odczytu cudzych danych.

Ten opis jest zanonimizowany i przepisany tak, aby nie ujawniać klienta, branży, hostów, prawdziwych endpointów ani identyfikatorów. Requesty i response’y pokazują mechanizm testu, a nie produkcyjne dane. Wszystkie wartości są syntetyczne: cand_AAAA, doc_BBBB, tenant-redacted, example.invalid.

Cel publikacji

Pokażemy, jak myślimy podczas testów BOLA/IDOR: od mapowania modelu danych, przez macierz uprawnień, po potwierdzenie wpływu bez pobierania realnych dokumentów klienta. To ma być merytoryczny case study, a nie gotowy exploit przeciwko konkretnemu systemowi.

Dwa konta, zero admina, pełna autoryzacja projektu

Dostaliśmy dwa zwykłe konta testowe. Konto A miało utworzone CV i jedną aplikację. Konto B było świeże, bez dokumentów. Nie było kodu źródłowego, dokumentacji API ani kont pracowników. Test był black-box z perspektywy normalnego użytkownika.

scope.txt
[+] konto A: kandydat z CV i aktywnym zgłoszeniem [+] konto B: kandydat bez dokumentów [-] brak admina, brak pracownika HR, brak dostępu do bazy [-] brak prawdziwych danych osobowych w PoC # Cel: separacja danych między kontami i odporność API na IDOR/BOLA.

Pierwsze 30 minut: model obiektów z samej przeglądarki

Nie zaczęliśmy od fuzzingu. Najpierw normalnie użyliśmy aplikacji i zapisaliśmy przepływy: upload CV, lista dokumentów, podgląd zgłoszenia, pobranie załącznika, usunięcie dokumentu. W Burp Suite powstała prosta mapa: kandydat → dokument → zgłoszenie → załącznik.

sanitized traffic — konto A
GET /api/v1/me HTTP/2 Authorization: Bearer token_A_REDACTED HTTP/2 200 OK { "candidateId": "cand_AAAA1111", "tenantId": "tenant_REDACTED", "roles": ["candidate"] } GET /api/v1/candidates/cand_AAAA1111/documents HTTP/2 Authorization: Bearer token_A_REDACTED HTTP/2 200 OK [ {"documentId":"doc_CV_AAAA","kind":"cv","fileName":"cv-redacted.pdf","ownerId":"cand_AAAA1111"} ]

Już tutaj widać kilka rzeczy istotnych dla pentestera: obiekty mają własne identyfikatory, API używa jawnego candidateId, odpowiedź zawiera ownerId, a pobranie dokumentu jest osobnym endpointem. To naturalny punkt do testu autoryzacji obiektowej.

Hipoteza

Jeżeli endpoint /documents/{documentId} bierze identyfikator i nie porównuje ownerId z użytkownikiem z tokena, mamy IDOR. Jeżeli problem dotyczy wielu obiektów, mamy systemowy BOLA, a nie pojedynczy błąd.

Nie testujemy jednego requestu. Testujemy relacje.

Dla każdego obiektu zbudowaliśmy mini-macierz: czy konto A może czytać A, czy konto B może czytać A, czy konto B może pobrać plik A, czy konto B może usunąć A, czy konto B może użyć dokumentu A w swoim zgłoszeniu. To ważne, bo część aplikacji blokuje zapis, ale zapomina o odczycie albo eksporcie.

authorization matrix
A reads A B reads A B downloads A B attaches A B deletes A profile OK 403 n/a n/a 403 document meta OK 200 n/a n/a 403 file download OK 302->200 302->200 n/a n/a application OK 200 n/a possible 403 comments OK 403 n/a n/a 403 [!] Najgorszy wzorzec: zapis zablokowany, odczyt i pobranie pliku otwarte.

Konto B czyta metadane dokumentu konta A

Najpierw potwierdziliśmy błąd bez pobierania pliku. To dobra praktyka: jeżeli sama odpowiedź z metadanymi dokumentu wystarcza do potwierdzenia naruszenia kontroli dostępu, nie trzeba dotykać realnego załącznika. W raporcie da się pokazać wpływ i minimalizować ekspozycję danych.

request/response — PoC 1: metadata IDOR
# Sesja: konto B. Obiekt: doc_CV_AAAA należy do konta A. GET /api/v1/documents/doc_CV_AAAA HTTP/2 Host: app.example.invalid Authorization: Bearer token_B_REDACTED Accept: application/json HTTP/2 200 OK Content-Type: application/json { "documentId": "doc_CV_AAAA", "ownerId": "cand_AAAA1111", "kind": "cv", "fileName": "cv-redacted.pdf", "mimeType": "application/pdf", "downloadUrl": "/api/v1/files/file_REDACTED/download", "createdAt": "2026-05-xxTxx:xx:xxZ" } Expected: 403 Forbidden albo 404 Not Found Actual: 200 OK + metadane dokumentu innego użytkownika
Dlaczego 404 bywa lepsze niż 403

Dla obiektów prywatnych często lepiej zwracać generyczne 404, żeby nie potwierdzać istnienia zasobu. Kluczowe jest jednak nie samo 403/404, ale egzekwowanie autoryzacji po stronie serwera.

Metadane były tylko początkiem

Drugi test dotyczył pobrania pliku. Response z metadanymi zawierał URL pobierania. Sprawdziliśmy, czy token B zostanie ponownie zweryfikowany przy endpointzie plikowym, czy aplikacja zakłada, że skoro ktoś dostał URL, to już może pobrać plik.

request/response — PoC 2: file download
# Nie pokazujemy prawdziwego pliku ani zawartości CV. GET /api/v1/files/file_REDACTED/download HTTP/2 Authorization: Bearer token_B_REDACTED HTTP/2 302 Found Location: /storage/private/signed/file_TOKEN_REDACTED GET /storage/private/signed/file_TOKEN_REDACTED HTTP/2 HTTP/2 200 OK Content-Type: application/pdf Content-Length: 184732 %PDF-1.7 % redacted — nie pobieramy ani nie publikujemy zawartości klienta

To był moment, w którym podatność z „błędu w metadanych” stała się pełnym naruszeniem poufności dokumentów. Technicznie: brak sprawdzenia subject(token) == owner(document) w warstwie dokumentów i/lub plików.

Severity: Critical

CV może zawierać imię, nazwisko, telefon, adres e-mail, zdjęcie, historię zatrudnienia, edukację, linki do profili i czasem dane szczególnych kategorii. Skala zależy od możliwości enumeracji identyfikatorów i liczby obiektów. W systemach rekrutacyjnych to realny problem RODO.

UUID nie jest kontrolą dostępu.
To tylko dłuższy identyfikator.

Gdzie zwykle leży błąd

API1

Autoryzacja obiektu tylko w UI

Frontend ukrywał cudze dokumenty, ale backend akceptował dowolny identyfikator dokumentu. UI nie jest boundary bezpieczeństwa.

MAPOWANIE → OWASP API1: Broken Object Level Authorization
API3

Nadmierne ujawnianie pól

Endpoint zwracał ownerId, downloadUrl i metadane, których UI nie potrzebował w każdym kontekście. To ułatwia rekonesans i łańcuchowanie.

MAPOWANIE → OWASP API3: Broken Object Property Level Authorization
API5

Różne reguły dla odczytu i zapisu

Delete/update były blokowane, ale read/download już nie. To typowy błąd: zespół chroni operacje modyfikujące, a nie docenia poufności odczytu.

MAPOWANIE → OWASP API5: Broken Function Level Authorization

Jak zamknąć tę klasę błędów, a nie jeden endpoint

1. Centralna funkcja autoryzacji obiektowej. Każdy endpoint przyjmujący ID zasobu powinien przejść przez wspólny mechanizm: pobierz obiekt → sprawdź tenant → sprawdź właściciela/rolę → dopiero potem zwróć dane.

2. Testy regresyjne BOLA. Dla każdego typu obiektu warto mieć automatyczny test: user A tworzy obiekt, user B próbuje read/update/delete/export/download. To powinno działać w CI.

3. Minimalizacja odpowiedzi. Nie zwracaj ownerId, downloadUrl, storageKey, internalStatus, jeżeli UI tego nie potrzebuje. Zmniejszasz powierzchnię rekonesansu.

pseudo-test regresyjny
create object as user_A -> object_id for action in [read, download, update, delete, attach, export]: response = call(action, object_id, token=user_B) assert response.status in [403, 404] assert response.body does_not_contain [ownerId, fileName, signedUrl, personalData]

Nie tylko “jest IDOR”, ale ile to kosztuje biznesowo

W raporcie technicznym pokazujemy requesty, macierz, root cause i rekomendacje. W executive summary tłumaczymy wpływ: potencjalny dostęp do danych kandydatów, ryzyko zgłoszenia naruszenia, utrata zaufania, obowiązki prawne, priorytet naprawy i zakres retestu.

To jest dokładnie typ luki, której nie widać na demo aplikacji. Wszystko wygląda poprawnie, dopóki ktoś nie wyjdzie poza ścieżkę UI i nie zacznie używać API tak, jak używałby go napastnik.

◆ BSTA · testpenetracyjny.pl

Masz API z dokumentami, plikami albo tenantami?

Sprawdzimy, czy Twoja aplikacja egzekwuje uprawnienia przy każdym obiekcie: od UI, przez API, po eksporty, załączniki i linki do plików.

Zamów test bezpieczeństwa →
Zakres + wycena w 2 dni robocze · raport techniczny + executive summary · pełna anonimizacja danych