LabbCybersäkerhet · Etisk hackning
0/0
Laboration

Åtkomstkontroll — serversidans auktorisering

Mål: Förstå varför bruten åtkomstkontroll är den vanligast förekommande kategorin i OWASP Top 10, och bygga korrekt auktorisering.

Observera

Genomför PortSwigger-akademins moduler om access control och IDOR (Insecure Direct Object Reference), eller motsvarande utmaningar i Juice Shop. Notera hur ofta problemet är att applikationen kontrollerar vad användaren kan se i gränssnittet snarare än vad användaren har rätt till på servern.

Analysera grundorsaken

Två klassiska mönster:

# Mönster 1: identifieraren litas på utan ägarkontroll
def visa_faktura(request, faktura_id):
    faktura = Faktura.objects.get(id=faktura_id)
    return render(request, "faktura.html", {"faktura": faktura})
    # Ingen kontroll av att fakturan tillhör den inloggade användaren
Vad koden gör — förklaring rad för rad

Det sårbara mönstret i Python. Funktionen hämtar en faktura utifrån numret i webbadressen — men kontrollerar aldrig att fakturan tillhör den som frågar.

def visa_faktura(request, faktura_id):
Funktionen tar emot anropet och det fakturanummer som stod i adressen.
Faktura.objects.get(id=faktura_id)
Hämtar fakturan med det numret ur databasen. Här finns felet: vilket nummer som helst fungerar.
return render(...)
Skickar tillbaka en färdig sida till besökaren.
# Ingen kontroll...
Kommentaren markerar vad som saknas. Byt siffran i adressen och du ser någon annans faktura.
// Mönster 2: kontrollen finns bara i gränssnittet
{användare.ärAdmin && <AdminPanel />}   // döljer knappen...
// ...men /api/admin/users saknar motsvarande kontroll på serversidan
Vad koden gör — förklaring rad för rad

Det andra sårbara mönstret, i JavaScript för gränssnittet. Knappen döljs för den som inte är administratör — men bara knappen.

{användare.ärAdmin && <AdminPanel />}
Läses som: om användaren är administratör, rita ut adminpanelen. Är hen det inte ritas ingenting ut.
//
Två snedstreck inleder en kommentar i JavaScript.
/api/admin/users
Adressen som panelen hämtar data från. Den går att anropa direkt, utan att gå via knappen — och saknar här en egen kontroll. Att dölja knappen är alltså ingen spärr.

Att dölja något i gränssnittet är inte åtkomstkontroll. Det enda som räknas är kontrollen på serversidan, vid varje anrop.

Bygg försvaret

1. Kontrollera ägarskap vid varje åtkomst — inte bara inloggning:

def visa_faktura(request, faktura_id):
    faktura = get_object_or_404(
        Faktura,
        id=faktura_id,
        organisation=request.user.organisation   # auktoriseringen ligger i frågan
    )
    return render(request, "faktura.html", {"faktura": faktura})
Vad koden gör — förklaring rad för rad

Den säkra versionen. Ägarkontrollen flyttas in i själva databasfrågan, vilket gör att den inte kan glömmas bort.

get_object_or_404(
Hämta posten, eller svara "finns inte" om den inte hittas. 404 är webbens kod för en sida som inte finns.
id=faktura_id,
Första villkoret: rätt fakturanummer.
organisation=request.user.organisation
Andra villkoret: och fakturan måste tillhöra den inloggades organisation. Båda villkoren måste stämma, annars hittas ingen post.
# auktoriseringen ligger i frågan
Poängen: det finns ingen separat kontroll som kan glömmas, eftersom kontrollen är en del av själva uppslagningen.

Att låta auktoriseringen ingå i databasfrågan är ett robust mönster: det går inte att glömma en separat if-sats som inte finns.

2. Neka som standard. Bygg systemet så att en ny endpoint är stängd tills någon aktivt öppnar den, i stället för tvärtom. Ett centralt mellanlager (middleware) som kräver explicit behörighetsdeklaration per route är ett sätt:

@krav_behorighet("faktura.las")
def visa_faktura(request, faktura_id):
    ...
Vad koden gör — förklaring rad för rad

En dekorator — ett kort sätt att hänga en regel på en funktion. Raden ovanför funktionen kräver en viss behörighet innan funktionen överhuvudtaget körs.

@
Snabel-a framför ett namn markerar en dekorator i Python. Den omsluter funktionen under och körs före den.
krav_behorighet("faktura.las")
Kräver behörigheten faktura.las. Saknas den körs aldrig funktionen.
...
Tre punkter betyder bara att resten av funktionen utelämnats i exemplet.

3. Använd oförutsägbara identifierare där det är lämpligt. UUID istället för löpnummer gör inte systemet säkert i sig — auktoriseringskontrollen är fortfarande det som skyddar — men det minskar risken att resurser hittas av misstag och försvårar kartläggning.

4. Testa behörighetsmatrisen systematiskt. Gör en tabell med roller i ena ledden och resurser i den andra, och verifiera varje kombination. Detta är tråkigt och exakt därför missas det ofta.

Roll Egen faktura Annans faktura Adminpanel
Anonym Nekas Nekas Nekas
Användare Tillåts Nekas Nekas
Admin Tillåts Tillåts Tillåts

Verifiera

Skriv automatiserade tester för de nekande fallen, inte bara de tillåtande. Ett test som verifierar att användare A inte kan hämta användare B:s data är mer värdefullt än ett test som verifierar att A kan hämta sin egen:

def test_annan_anvandare_nekas(client, anvandare_a, faktura_tillhor_b):
    client.force_login(anvandare_a)
    svar = client.get(f"/fakturor/{faktura_tillhor_b.id}/")
    assert svar.status_code == 404   # inte 200
Vad koden gör — förklaring rad för rad

Ett automatiskt test. Det bevisar att användare A inte kommer åt användare B:s faktura — och det är det testet som faktiskt har värde.

def test_annan_anvandare_nekas(...)
Testfunktioner namnges så att namnet beskriver vad som ska gälla. Verktyget hittar dem på att namnet börjar med test.
client.force_login(anvandare_a)
Logga in som användare A i testet.
client.get(f"/fakturor/{...}/")
Försök hämta fakturan som tillhör B.
assert svar.status_code == 404
assert betyder "detta måste stämma, annars underkänns testet". Här krävs svaret 404, alltså nekad. Hade det blivit 200 vore sårbarheten kvar.

Dokumentera

Bifoga behörighetsmatrisen i rapporten och markera de kombinationer som brast. Detta ger beställaren en konkret bild av omfattningen.

Reflektionsfrågor: Varför är "neka som standard" ett starkare designval än att lägga till kontroller efterhand? Varför bör svaret vara 404 snarare än 403 i vissa fall?


Kunskapskontroll

Välj ett svar per fråga. Du får återkoppling direkt, och avsnittet markeras som klart när alla frågor är besvarade.

1Att dölja en adminknapp i gränssnittet för användare utan behörighet är:

2Vilket mönster är mest robust för att kontrollera att en resurs tillhör den inloggade användaren?

3Vilket test är mest värdefullt för åtkomstkontroll?