Ebaturvaline otsene objektiviide (insecure direct object reference, IDOR) tekib kõikjal, kus teie rakendus võtab kliendilt vastu identifikaatori ning tagastab või muudab vastava kirje, küsimata, kas see kasutaja tohib sellele üldse ligi pääseda. API maailmas nimetatakse sama viga katkiseks objektitaseme autoriseerimiseks (broken object-level authorisation, BOLA) ja see on OWASP API Security 2023. aasta nimekirja tipus. Selle ärahoidmiseks on vaja autoriseerimiskontrolli igal objektile juurdepääsul, mis on rakendatud kogu rakenduses ja mida katavad testid.
Millest IDOR koosneb ja kus see peidus on
OWASP IDOR-i spikker jagab IDOR-i kolmeks osaks: objekt (konto, dokument, pilet, tehing), viide sellele (ID, UUID, kontonumber, token või slug) ja puuduv objektitaseme kontroll. Viga ongi kolmas osa.
Viide ei ole alati URL-is kujul /users/123. See võib olla peidetud vormiväljal, näiteks user_id, päringustringis, päringu päises või JSON-i sisus. Viide võib olla ka failinimi: kui GET /documents/annual-report.pdf töötab, proovige mõnda teist kehtivat dokumendinime.
OWASP API Security BOLA kirjeldus hindab ärakasutatavuse lihtsaks, levimuse laialdaseks ja tuvastatavuse lihtsaks. API-serverid ei jälgi tavaliselt kliendi olekut, mistõttu toetuvad nad otsustamisel, millist objekti laadida, kliendi saadetud ID-dele. OWASP-i näidisstsenaariumid:
- Poeanalüütika lõpp-punkt kujul
/shops/{shopName}/revenue_data.jsonkoos teise lõpp-punktiga, mis loetleb kõik poenimed, annab lihtsale skriptile kätte tuhandete kaupluste müügiandmed. - Sõidukite API, mis võtab vastu VIN-koodi, kuid ei kontrolli kunagi, et sõiduk kuulub sisselogitud kasutajale.
- GraphQL-i mutatsioon
deleteReports, mis kustutab mis tahes dokumendi, mille ID talle antakse.
Viimane juhtum on kustutamine, nii et testige uuendamisi ja kustutamisi sama hoolikalt kui lehte, mis kirjet kuvab.
Testige kahe kontoga
Looge kasutajad A ja B, andke kummalegi oma objektid (dokumendid, piletid, arved), autentige end kasutajana A ja saatke päringuid uuesti B identifikaatoritega.
- Looge kaks kontot erinevate autoriseerimisulatustega.
- Looge mõlemale kasutajale kuuluvad objektid.
- Logige sisse kasutajana A ja salvestage iga päring, mis sisaldab objektiviidet.
- Asendage viited kasutaja B viidetega teel, päringus, sisus ja päistes.
- Veenduge, et rakendus keelab juurdepääsu iga kord.
Katke lugemine, loomine, uuendamine, kustutamine, eksport ja administraatoritoimingud. Ärge jätke lõpp-punkti vahele põhjusel, et selle identifikaatorid tunduvad juhuslikud. Spikri järgi tuleb juurdepääs keelata sõltumata sellest, kas identifikaator on ennustatav või arvamatu.
Tehke sellest regressioonitest
Kui käsitsi läbimine töötab, muutke see koodiks. Allpool on pytesti visand, mis eeldab fixture'e, mis annavad kasutaja A sisselogitud sessiooni ja kasutajale B kuuluva dokumendi ID. Iga päring peab olema muus osas kehtiv ja korrektse sisuga, et sisendi valideerimisest tulev 400 või 415 ei saaks testi valel põhjusel läbida:
import os
import pytest
BASE_URL = os.environ["APP_BASE_URL"] # from your test config
DENIED = 404 # your chosen response for foreign IDs
ENDPOINTS = [
("GET", "/documents/{id}", None),
("PUT", "/documents/{id}", {"title": "changed"}),
("DELETE", "/documents/{id}", None),
("GET", "/documents/{id}/export", None),
("POST", "/documents/{id}/comments", {"text": "hello"}),
]
@pytest.mark.parametrize("method,path,body", ENDPOINTS)
def test_a_cannot_touch_bs_document(method, path, body, session_a, b_document_id):
url = BASE_URL + path.format(id=b_document_id)
r = session_a.request(method, url, json=body)
assert r.status_code == DENIED
Valige 403 ja 404 vahel teadlikult. Võõra ID korral tagastatud 403 kinnitab, et selline ID on olemas, mis aitab ründajal ID-sid läbi proovida. Kumma te ka valite, kontrollige alati ainult seda ühte koodi. Genereerige lõpp-punktide loend oma marsruuditabelist, et uued lõpp-punktid tekiksid automaatselt, ja blokeerige juurutused, kui testikomplekt ebaõnnestub.
GraphQL vajab kaht lisajuhtumit. Päringustage võõrast ID-d puu igal tasemel, et näha, kas pesastatud resolverid kordavad ülemobjekti kontrolli. Hulgitoimingute puhul, näiteks eespool nimetatud reportKeys massiiv, saatke päring, mis segab teie enda ID-d kellegi teise omadega, ja veenduge, et kogu kutse ebaõnnestub või võõras element jäetakse vahele.
Piirake otsing kasutajaga
Kontrollige autoriseerimist iga objekti jaoks, millele kasutaja ligi püüab pääseda, kasutades oma raamistiku pakutavat mehhanismi, nii et kontroll on struktuurne ega sõltu sellest, kas iga arendaja seda meeles peab. Otsesem muster on otsida objekte hulgast, millele praegune kasutaja ligi pääseb. Rails'i variant:
# vulnerable, searches all projects
@project = Project.find(params[:id])
# secure, searches projects related to the current user
@project = @current_user.projects.find(params[:id])
Spikris olev Spring Booti käsitleja kutsub välja findById(id) ning tagastab seega mis tahes dokumendi igaühele, kes oskab ID ära arvata või selle hankida. Allolev lõik on meie enda näide, mitte OWASP-i kood. See eeldab Spring Data tuletatud päringut väljal ownerId ning currentUser() tähistab teie abifunktsiooni, mis loeb usaldusväärsest autentimiskontekstist:
@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
return documentRepository
.findByIdAndOwnerId(id, currentUser().getId())
.orElseThrow();
}
Kus võimalik, vältige identifikaatorite paljastamist ja tuletage kasutaja sessioonist. Mitmeastmelistes voogudes hoidke identifikaatoreid sessioonis, et klient ei saaks neid sammude vahel muuta.
Juhuslikud identifikaatorid aitavad, kuid ainult kaitse sügavuti osana. Lisage juhusliku stringi veerg ja kasutage seda URL-ides või kasutage primaarvõtmetena UUID-sid. Ärge krüpteerige ID-sid nende peitmiseks, sest seda turvaliselt teha on keeruline.
Mida koodiülevaatusel kontrollida
Lugege läbi iga käsitleja, mis võtab kliendi antud ID, ja leidke, kus õiguste kontroll toimub. Vaadake kõigepealt, kas päring käib kasutajale piiratud hulga vastu või kutsub välja globaalse find-i ja kontrollib alles hiljem, kui üldse. Seejärel veenduge, et kontroll on olemas igas funktsioonis, mis jõuab kliendi sisendi põhjal kirjeni, sealhulgas uuendamisel, kustutamisel ja ekspordil.
Olge tähelepanelik käsitleja suhtes, mis võrdleb ainult sessioonikasutaja ID-d (näiteks JWT-st välja võetud, mida saate uurida meie JWT tööriistaga) ID parameetriga. OWASP märgib, et see katab vaid väikese osa juhtudest. Autoriseerimine peab järgima kasutajapoliitikaid ja hierarhiat. Mitme rentnikuga (multi-tenant) süsteemis võib rentniku X administraator küsida arvet, mis kuulub rentnikule Y. Kasutaja on autenditud, roll on administraator ja ID on kehtiv, seega võib lihtne rollikontroll läbi minna, kuigi objekt asub vales rentnikus.
Hoidke sorteerimisel kaks viga lahus. BOLA puhul on kasutajal lõpp-punkti kutsuda disaini järgi lubatud ja rikkumiseks on manipuleeritud ID. Kui kasutaja pääseb lõpp-punkti, kuhu tal üldse asja ei ole, on tegu katkise funktsioonitaseme autoriseerimisega, mille parandus on teistsugune.
Kust alustada
- Koostage loend igast lõpp-punktist, vormist ja GraphQL-i operatsioonist, mis võtab vastu identifikaatori, sealhulgas failinimedest ja peidetud väljadest. Alustage oma OpenAPI spetsifikatsioonist või marsruuditabelist ning lisage siis see, mis ilmneb vaid proxy ajaloos rakenduses klõpsates.
- Tehke ülaltoodud kahe kasutajaga läbimine kõigepealt kümnele tundlikumale objektile, näiteks arved, isikuandmed ja administraatoritoimingud, ning salvestage iga vastus, mis ei ole keeld.
- Asendage globaalsed otsingud kasutajale piiratud otsingutega, kasutades oma raamistiku autoriseerimismehhanismi.
- Viige parametriseeritud kasutajatevahelised testid oma pipeline'i ja laske ebaõnnestumisel väljalase blokeerida.
- Lisage ülevaatusküsimused oma pull request'i malli. Väliseks kontrolliks, mida teie testid kahe silma vahele jätsid, saate tellida meie penetratsioonitestimise teenuse.
