Hoppa yfir í efnið

Kökur, lotur og JWT

Að innskráningu lokinni verður vefurinn að geta borið kennsl á notandann í næstu beiðni. Bókunin HTTP man ekkert af sjálfu sér milli beiðna og því þarf vafrinn að senda frá sér eitthvert gildi sem tengir hina nýju beiðni við fyrra auðkenningarskref.

Stundum er þetta gildi einungis slembið lotuauðkenni og gögnin eru geymd hjá þjóninum. Stundum ber táknið sjálft með sér upplýsingar, áþekkt því sem gerist í JWT. Í báðum tilvikum er spurning CTF-rannsakandans sú sama: hvaða gildi tengir beiðnina við notanda, hver stjórnar því og hvað sannreynir þjónninn?

Verkefnið Pappírsundirskrift útvegar okkur JWT-tákn sem fullyrðir að við séum aðeins venjulegur notandi. Stjórnendaleið les hlutverkið úr tákninu. Áskorunin felst í því að komast til botns í hvort þjónninn sannreyni í raun undirskriftina, eða láti sér nægja að lesa fullyrðinguna af „pappírnum“.

Lotutákn eru aðgangsgögn

Notast skal við kökur og tákn úr eigin gervilotu og það eingöngu í heimiluðum verkefnum. Lotuauðkenni kann að veita nákvæmlega sama aðgang og aðgangsorð, á meðan það heldur enn gildi sínu. Ekki birta raunveruleg tákn í skjáskotum, lausnarskýrslum eða spjalli.

Frá innskráningu að veflotu

Veflota (e. web session) tengir saman fjölda sjálfstæðra HTTP-beiðna undir einu og sama ástandinu. Eftir vel heppnaða innskráningu gæti þjónninn svarað:

HTTP/1.1 302 Found
Location: /account
Set-Cookie: session=K7m3pQ9x...; Path=/; HttpOnly; Secure; SameSite=Lax

Vafrinn geymir kökuna og sendir hana síðar:

GET /account HTTP/1.1
Host: nordurmarkadur.example
Cookie: session=K7m3pQ9x...

Gildið K7m3pQ9x... gæti verið lotuauðkenni (e. session identifier). Þjónninn notar það sem lykil í eigin geymslu:

K7m3pQ9x... → notandi: anna, hlutverk: user, innskráð: já

Samkvæmt þessu líkani er það ekki kakan sjálf sem fullyrðir að notandinn sé Anna. Hún bendir aðeins á færslu sem þjónninn geymir. Tilviljanakennt, ógegnsætt gildi gefur því oft lítið við Base64-afkóðun. Ef verkefnið gefur engar aðrar vísbendingar er löng slembin runa ekki góð ástæða til að byrja að giska á lotuauðkenni.

Hver lota hefur sinn líftíma. Hún verður til, tekur þátt í mörgum beiðnum, og getur að lokum runnið út á tíma eða verið ógilt beinlínis við útskráningu. Lotan getur auk þess breyst við ákveðin skrefapunkt, svo sem til dæmis þegar óinnskráður gestur verður auðkenndur notandi.

Þjónninn kemur Set-Cookie fyrir í svari. Vafrinn sendir svo Cookie með í för í síðari beiðni. Þar er bersýnilega um tvo gerólíka hausa að ræða:

Set-Cookie: session=abc123; Path=/; HttpOnly
Cookie: session=abc123; theme=dark

Eitt og sama Cookie-hausgildið getur borið margar kökur, þá aðskildar með semíkommu. Eitt svar getur hins vegar haft margar aðskildar Set-Cookie-línur.

Í DevTools birtast þessar kökur gjarnan undir flipanum Application eða Storage. Network sýnir hvaða kökur fóru með tiltekinni beiðni og hvaða Set-Cookie kom í svari. Burp HTTP history sýnir sömu hausa án þess að fela milliskref.

Með curl má vista og endurnota kökur:

curl -i -c cookies.txt https://nordurmarkadur.example/login
curl -i -b cookies.txt https://nordurmarkadur.example/account

Valkosturinn -c skrifar kökurnar í geymslu í kjölfar svarsins og -b les þær síðan upp fyrir næstu beiðni. Þegar bæði eru notuð í hverju skrefi getur curl tekið við nýjum kökum og sent gildandi kökur áfram:

curl -i -c cookies.txt -b cookies.txt https://nordurmarkadur.example/account

Eiginleikar kökunnar ráða því hvenær vafrinn sendir hana

Textinn sem fylgir í kjölfar kökugildisins í hausnum Set-Cookie samanstendur af eiginleikum hennar (e. attributes). Þessir eiginleikar teljast þó ekki hluti sjálfs gildisins.

Eiginleiki Hagnýt merking í vafra
Secure Vafrinn sendir kökuna aðeins yfir HTTPS
HttpOnly JavaScript fær ekki að lesa kökuna með document.cookie
SameSite Takmarkar hvenær kakan fylgir beiðnum sem hefjast frá öðru vefsvæði
Path Takmarkar hvaða slóðir fá kökuna
Domain Stýrir hvaða hýsar innan léns fá kökuna
Expires / Max-Age Segir hvenær vafrinn á að hætta að geyma kökuna

Vert er að nefna að Secure-eiginleikinn dulkóðar innihald kökunnar síst af öllu; HTTPS gegnir því hlutverki að vernda flutninginn. HttpOnly varðveitir kökuna heldur ekki hulda sjónum fyrir Network-flipanum, Burp eða sjálfum þjóninum, og dregur sömuleiðis engan úr mætti vafrans til þess að senda hana. Í gagnaglímum er HttpOnly-eiginleikinn því ótvíræð vísbending sem mælir gegn lausn sem reiðir sig á document.cookie, þó hann komi á engan hátt í veg fyrir að þú endursendir eigin gerviköku í Repeater.

Eiginleikinn Path=/admin segir vafranum til dæmis að senda ekki kökuna þegar leitað er inn á /account. Slíkt sannar þó á engan hátt að kakan veiti stjórnandaréttindi. Path lýsir aðeins afhendingarreglu vafrans, og er alls ekki aðgangsstýring af hálfu þjónsins.

Domain=nordurmarkadur.example kann að leyfa köku að ferðast til viðeigandi undirléna, en kaka án Domain-eiginleikans er talsvert bundnari við þann hýsi sem kom henni fyrir. Tvær kökur geta haft sama heiti en ólíkt Path eða Domain. Ef Burp sýnir tvö session-gildi skaltu skoða uppruna þeirra áður en þú eyðir öðru.

SameSite-eiginleikinn styðst yfirleitt við gildin Strict, Lax eða None; nútímavafrar krefjast að jafnaði Secure-eiginleikans ef gildið er stillt á None. Eiginleikinn snýst um vefsvæði (e. site), sem er ekki nákvæmlega sama afmörkun og uppruni. Við skoðum áhrif þess á fölsun beiðna milli vefja í CSRF-kaflanum. Hér nægir að vita að SameSite hefur áhrif á sjálfvirka kökusendingu vafrans en ekki á beina beiðni sem þú smíðar með eigin gerviköku.

Lotuauðkenni fyrir og eftir innskráningu

Margir vefir stofna til lotu langtum áður en notandi skráir sig inn, til að mynda til þess að varðveita innihald innkaupakörfu eða svokallað CSRF-tákn. Þegar innskráning tekst getur þjónninn gefið nýtt lotuauðkenni.

fyrir innskráningu:  session=GESTUR_7f2a
 eftir innskráningu: session=ANNA_c91e

Ef þjónninn heldur sama auðkenni og tengir það síðan við innskráðan notanda getur komið upp lotufesting (e. session fixation): einhver sem gat valið eða þekkti auðkennið fyrir innskráningu gæti haldið aðgangi eftir hana.

Í heimiluðu gagnaglímuverkefni gefst kjörið tækifæri til að rannsaka þetta með tveimur eigin gervilotum. Skráðu kökuna fyrir innskráningu, skráðu gervinotandann inn og berðu svo Set-Cookie og næstu Cookie saman. Ef auðkennið breytist hefur þjónninn framkvæmt lotusnúning (e. session rotation), sem mælir gegn einfaldri lotufestingu.

Ekki prófa að festa lotu annars notanda eða senda honum tengil. Byrjendaverkefni getur sýnt hugmyndina með tveimur vöfrum sem þú stjórnar eða með tilbúnum beiðnum.

Útskráning og gildistími

Útskráning knýr vafrann vitaskuld oft á tíðum til þess að eyða köku:

Set-Cookie: session=; Max-Age=0; Path=/

Mundu hins vegar að þar skiptir tvennt höfuðmáli: Annars vegar að vafrinn kasti gildinu fyrir róða, og hins vegar að þjónninn geri lotuna formlega ógilda. Í CTF má varðveita afrit af eigin gerviköku, skrá sig út og athuga hvort sama afrit fái enn svar. Samþykki þjónninn kökuna áfram er hugsanlegt að viðmótið hafi aðeins eytt kökunni í sjálfum vafranum.

Einnig er rétt að halda til haga að lotur lúta gjarnan ákveðnum óvirknistíma eða föstum lokatíma. Þótt JWT-tákn skarti ósjaldan hinni svokölluðu exp-kröfu, geymir hin dæmigerða þjónslota tímaviðmiðin nánast ætíð hjá sjálfum þjóninum. Fáist 401-svar að nokkrum mínútum liðnum bendir það því stundum fremur til útrunninnar lotu en rangrar slóðar eða ógilds hauss.

Sé niðurstaðan háð tíma skaltu skrá af nákvæmni hvenær táknið var gefið út og endurtaka leikinn með ferskri gervilotu. Sögufrægt tákn sem hrasar einungis einu sinni upplýsir okkur hvorki um eitt né neitt er varðar hina innri byggingu þess.

JWT er snið fyrir fullyrðingar

JWT, JSON Web Token, er textasnið sem skiptist í þrjá hluta með punktum:

haus.nytjahlass.undirskrift

Dæmi gæti litið svona út:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiJhbm5hIiwicm9sZSI6InVzZXIiLCJleHAiOjIwMDAwMDAwMDB9.
UNDIRSKRIFT

Hér hefur verið línuskipt einvörðungu til aðgreiningar; raunverulegt tákn stendur ávallt sem ein samfelld lína.

Fyrstu tveir hlutarnir eru venjulega Base64URL-kóðað JSON. Base64URL notar - og _ í stað sumra tákna venjulegs Base64 og sleppir oft =-fyllingu.

Við afkóðun gæti hausinn hæglega litið svona út:

{
  "alg": "HS256",
  "typ": "JWT"
}

Hér tiltekur alg reikniritið sem táknið þykist styðjast við. Afkóðaða nytjahlassið gæti á hinn bóginn tekið á sig þessa mynd:

{
  "sub": "anna",
  "role": "user",
  "exp": 2000000000
}

Reitirnir kallast kröfur (e. claims). sub auðkennir viðfang táknsins, role er sérsniðin krafa og exp er lokatími sem Unix-tímastimpill.

JWT er ekki sjálfkrafa lota

JWT getur verið notað sem lotutákn, aðgangstákn eða til annarra fullyrðinga. Að sjá þrjá hluta aðskilda með punktum gefur okkur til kynna á hvaða sniði táknið er, en útskýrir engan veginn hvar það er sent né heldur við hvaða heimilanir þjónustan styðst við það.

Afkóðun er ekki sannprófun

Sérhverjum þeim er fær JWT-tákn í hendur reynist það að öllu jöfnu leikur einn að afkóða bæði haus og nytjahlass. Sú aðgerð útheimtir alls engan leynilykil. Eftirfarandi Python-brot afkóðar þessa tvo fyrstu hluta:

import base64
import json


def decode_part(part):
    padded = part + "=" * (-len(part) % 4)
    raw = base64.urlsafe_b64decode(padded)
    return json.loads(raw)

header_part, payload_part, signature_part = token.split(".")
print(decode_part(header_part))
print(decode_part(payload_part))

Að sjá role: admin eftir afkóðun sannar ekki að táknið sé gilt. Undirskriftin tengir fyrstu tvo hlutana við lykil sem sannreynjandinn treystir. Breytist svo mikið sem einn stafur í nytjahlassinu ber allri réttri sannprófun að hafna fyrri undirskriftinni.

base64url(haus) + "." + base64url(nytjahlass)
                         │
                         └── undirskrift reiknuð eða sannreynd með lykli

Táknið (JWT) sjálft er heldur ekki ósjálfrátt dulkóðað. Notandanafn, hlutverk og aðrar kröfur í venjulegu undirrituðu JWT eru læsilegar. Ráðlagt er að trúa óþekktum vefsíðum eða afkóðurum (e. decoders) aldrei fyrir raunverulegum leyndarmálum; leggðu þig þess í stað fram um að afkóða CTF-táknin ýmist staðbundið eða inni í DevTools, allt eftir því sem verkefnið gefur svigrúm til.

Hvar fara JWT-tákn?

Vefur getur sett JWT í köku:

Set-Cookie: auth_token=eyJ...; Path=/; HttpOnly; Secure

Við svo búið mun vafrinn að líkindum koma því fyrir í Cookie-haus líkt og lög gera ráð fyrir um aðrar kökur. Forritaskil nota einnig oft Authorization:

GET /api/account HTTP/1.1
Host: nordurmarkadur.example
Authorization: Bearer eyJ...

Lykilorðið Bearer merkir að hér sé á ferðinni svokallað handhafatákn (e. bearer token). Handhafi sem mætir með slíkt tákn fær sjálfkrafa þann aðgang sem það veitir, algjörlega án þess að sanna þurfi tilvist sérstaks einkalykils í sömu beiðni.

Suma viðmótshönnuði fýsir að fela táknið í localStorage til að láta JavaScript síðan skjóta því inn í hausinn. Eigir það við rök að styðjast, ættir þú að geta grafið það upp, bæði inni í Storage-flipanum sem og í sjálfri beiðninni í Network. Forðastu að giska í blindni á geymslustaðinn; fylgdu tákninu heldur eftir, annaðhvort úr svari eða JavaScript-kóða, yfir í næstu beiðni.

Algengar JWT-tilgátur í CTF

Við glímu við JWT-verkefni er allur ávinningur af því að byrja á því að prófa hundrað ólíkar nytjahleðslur heldur fátæklegur. Afkóðaðu fyrst haus og nytjahlass, breyttu einu atriði og athugaðu hvernig þjónninn svarar.

Undirskrift er ekki sannreynd

Þetta er veikleikinn í Pappírsundirskrift. Þjónninn afkóðar táknið og les role, en athugar ekki hvort undirskriftin passi. Þá getur breytt nytjahlass verið samþykkt þótt undirskriftin sé gömul eða merkingarlaus.

Ótvíræð staðfesting fæst síðan fram með samanburði: upprunalegt role: user fær höfnun, en sama tákn með aðeins breyttu hlutverki fær annað svar. Skili hins vegar sérhver breyting 401-svarinu má telja víst að undirskriftin hafi sannarlega verið sannreynd, og fellur þá tilgátan um veikleikann um sjálfa sig.

Reiknirit og lykill passa ekki við væntingar

Hausinn getur nefnt alg: none, samhverft HMAC-reiknirit eins og HS256 eða ósamhverft reiknirit eins og RS256. Eldri verkefni, eða jafnvel viljandi gölluð, freistast gjarnan til þess að treysta reikniritinu beint upp úr ótraustum haus, samþykkja heitið none eða rugla einfaldlega saman gerðum lykla.

Varastu að breyta reikniritinu af einskærum vana. Skoðaðu fyrst hvaða reiknirit upprunalega táknið notar og hvernig verkefnið gefur til kynna að þjónninn vinni úr því. Ef none-tilraun fær sömu 401-höfnun og önnur ógild tákn er líklegt að sú leið sé lokuð.

HS256 notar sameiginlegt leyndarmál. Bjóði verkefnið upp á mjög lokað og afmarkað lyklamengi er í lagi að prófa það staðbundið og máta gegn undirskriftinni. Slíkt er auðvitað allt annað og háttvísara en að hamra á þjóninum með ágiskunum um lykilinn. Langur slembillykill án viðhengdra, verkefnissértækra orðamengja, hlýtur að teljast sterk vísbending gegn raunhæfni þrautleitar.

Kröfur eru ekki aðeins role

Þjónn getur sannreynt undirskrift en gleymt að athuga merkingu krafna:

  • exp segir hvenær tákn rennur út,
  • nbf segir hvenær það má fyrst taka gildi,
  • iss nefnir útgefanda,
  • aud nefnir ætlaðan viðtakanda,
  • sub nefnir viðfang eða notanda.

Tákn, sem annaðhvort er rétt undirritað en ætlað öðru forriti eða hreinlega útrunnið á tíma, skal alls ekki fá sjálfkrafa brautargengi hér. Í CTF getur verkefnið gefið tvö tákn og spurt af hverju annað er samþykkt. Undir slíkum kringumstæðum skulum við bera saman hvort tveggja undirskriftarstöðuna og kröfurnar sjálfar; Base64-afkóðun ein og sér verður fátt um svör.

Hausinn getur einnig haft kid, lykilauðkenni sem hjálpar þjóninum að velja sannprófunarlykil. Leiði notandastýrt kid okkur á vit óvæntrar lyklaleitar kann að vera um sjálfstæðan veikleika að ræða. Gættu þess þó, að án skýrra vísbendinga um lykilvalið eru lítil rök fyrir því að þreifa fyrir sér með slóðaflakki eða innskotum í hvert einasta JWT-verkefni.

Göngudæmi: Pappírsundirskrift

Opnaðu verkefnið og finndu táknið í Network eða Storage. Staðfestu fyrst hvar það fer til þjónsins: er það í köku eða Authorization-haus? Afritaðu aðeins gervitáknið úr verkefninu.

Gerðu þér mat úr því að kljúfa táknið við punktana og afkóða fyrstu tvo hlutana á staðbundinn hátt. Kannaðu vandlega tilvist alg, role sem og annarra krafna. Greini nytjahlassið til að mynda frá role: user, er komin fram skýr og afmörkuð breyta sem bíður þess eins að vera prófuð.

Stiklum nú á stóru í gegnum eftirfarandi Python-brot. Það hefur þann kost að varðveita bæði hausinn og hina fyrri undirskrift, á sama tíma og það töfrar fram glænýtt nytjahlass:

import base64
import json


def decode_json(part):
    padded = part + "=" * (-len(part) % 4)
    return json.loads(base64.urlsafe_b64decode(padded))


def encode_json(value):
    raw = json.dumps(value, separators=(",", ":")).encode()
    return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()

header_part, payload_part, signature_part = token.split(".")
payload = decode_json(payload_part)
payload["role"] = "admin"
forged_token = ".".join([
    header_part,
    encode_json(payload),
    signature_part,
])
print(forged_token)

Kóðinn gerir hins vegar kröfu til þess að þú fáir breytuna token úr verkefninu þínu; þessi kafli geymir hvorki gild tákn né undirskriftir. Ef undirskriftin væri tekin til eðlilegrar sannprófunar hlyti forged_token eðli máls samkvæmt að vera kastað, enda var undirskriftin reiknuð út frá upphaflega nytjahlassinu.

Sendu breytta táknið á /admin á sama stað og upprunalega táknið var sent. Ef það er kaka getur beiðnin litið svona út:

BASE_URL='https://slod-verkefnisins.example'

curl -i "$BASE_URL/admin" \
  -b 'auth_token=<breytta táknið>'

Ef verkefnið notar annað kökuheiti skaltu varðveita það. Ef það notar Authorization, sendirðu þess í stað:

curl -i "$BASE_URL/admin" \
  -H 'Authorization: Bearer <breytta táknið>'

Berðu svarið saman við beiðni með upprunalega tákninu. Breytt hlutverk ásamt óbreyttri undirskrift er meðvituð tilraun til að prófa eina forsendu: hvort undirskriftin sé sannreynd. Fyrir alla muni, gættu þess að varpa ekki samtímis inn alg: none, nýju sub og útrunnu exp. Slík framkoma blindar okkur algjörlega í þeim efnum að greina hvaða breyting olli í raun og veru straumhvörfunum.

Hagnýtar vísbendingar um annað

Ef tákn er aðeins löng slembin runa án punkta er það líklega ekki JWT. Hljóti minnsta breyting á tákninu ávallt 401-höfnun, þó svo að táknið virki óbreytt, verður að teljast líklegt að heilleiki gagnanna sé vandlega sannreyndur. Ef þjónninn hafnar útrunnu tákni, röngum aud og breyttu role með sama skýra hætti eru einföld kröfubreyting og vöntun á gildistímaprófi ólíklegar leiðir.

Fari svo að táknið byggi á RS256-reikniritinu verður að hafa í huga að þjóninum er fullkomlega ónauðsynlegt að beita einkalyklinum við sannprófunina; dreifilykillinn einn sér fyllilega til. Opinber dreifilykill er því ekki sjálfkrafa leyndarmál eða fáni. Í sömu andrá minnumst við þess að hið afkóðaða nytjahlass færir einungis sönnur á þær fullyrðingar sem táknið hefur uppi, alls ekki að þjóninum sé unnt að treysta þeim.

Tileinkaðu þér þann góða sið að skrásetja af nostursemi upprunalega beiðni, staka breytingu sem þú kannt að framkvæma, og vitaskuld útkomuna. JWT-verkefni verða fljótt ruglingsleg ef mörg næstum eins tákn liggja í glósunum án þess að ljóst sé hvaða undirskrift og nytjahlass tilheyra saman.

Samantekt

Kaka er geymslu- og afhendingarleið vafrans. Lota er ástandið sem tengir beiðnir saman. Lotuauðkenni getur bent á þjónsgeymslu, en JWT getur borið læsilegar kröfur í tákninu sjálfu. Hvorugt atriðið færir þó sönnur á að þjónninn hafi sannreynt bæði réttan notanda og heimildir hans. Það fellur alfarið í okkar skaut að elta gildið alla leið inn í raunverulega beiðni, og bera svörin gaumgæfilega saman.

Secure, HttpOnly, SameSite, Path og Domain lýsa því hvenær vafri meðhöndlar köku. Lotusnúningur við innskráningu getur mælt gegn einfaldri lotufestingu, og útskráning þarf að skoðast bæði í vafra og hjá þjóninum. Í JWT er Base64URL-afkóðun aðeins lestur. Undirskrift, reiknirit, lykill, gildistími og kröfur ráða hvort fullyrðingin á að vera tekin gild.

Í næsta kafla höldum við auðkenndri gervilotu en breytum spurningunni: þótt þjónninn viti hver notandinn er, athugar hann þá í hverri beiðni hvort notandinn megi nálgast tiltekinn reikning eða aðgerð?