SQL-innskot
Leitarsíða tekur við texta, gagnagrunnur framkvæmir leitina og vefurinn birtir loks niðurstöðurnar. Milli þessara þriggja skrefa ferðast gögn yfir einkar mikilvæg mörk. Við viljum að leitarorðið sé meðhöndlað einvörðungu sem gögn inni í SQL-fyrirspurninni. Lími forritið textann hins vegar beint inn í fyrirspurnina kann svo að fara að hluti hans verði skyndilega ræður um SQL-setningafræði.
Þetta kallast SQL-innskot (e. SQL injection, SQLi). Í gagnaglímum ætluðum byrjendum þurfum við engin hundrað tilbúin sýnishorn af nytjahleðslum (e. payloads). Við þurfum þvert á móti að finna inntaksstaðinn, átta okkur á samhenginu, breyta svo aðeins einu skilyrði og lesa loks í þann mismun sem svarið kann að sýna.
Verkefnið Bókasafnið býður upp á leit í gegnum safn af tilbúnum bókum. Hefðbundin leit sýnir auðvitað aðeins opinberar færslur, en ein sérstök falin færsla leynist í sömu gervitöflu. Markmiðið er að staðfesta með óyggjandi hætti að leitartextinn sé túlkaður sem gildur SQL-kóði og ginna svo gagnagrunninn til að skila hinni foldu færslu.
Aðeins tilbúni gagnagrunnurinn
Notaðu SQL-tilraunirnar aðeins á verkefnið sem kaflinn vísar á eða eigin staðbundinn gagnagrunn. Ekki setja gæsalappir, skilyrði eða sjálfvirka gagnagrunnsgreiningu í leitarreiti raunverulegra vefja. Verkefnið krefst hvorki skönnunar né breytinga á gögnum.
Tafla, röð og dálkur
Gagnagrunnur geymir skipulögð gögn. Venslagagnagrunnur setur gögn oft fram sem töflur. Einföld products-tafla gæti verið:
| id | name | description | visible |
|---|---|---|---|
| 1 | Lopapeysan | Hlý bók um ull | 1 |
| 2 | Norðurljós | Leiðarvísir um veturinn | 1 |
| 3 | Bakherbergið | Falin gervifærsla | 0 |
Hver lína telst röð og hver nefndur eiginleiki er dálkur. SQL er tungumál sem gagnagrunnskerfið túlkar. Fyrirspurn sem sækir sýnilegar vörur gæti verið:
SELECT name, description
FROM products
WHERE visible = 1;
SELECT velur dálka, FROM velur töflu og WHERE afmarkar raðir. Leit bætir við skilyrði:
SELECT name, description
FROM products
WHERE visible = 1
AND name LIKE '%ull%';
LIKE ber texta saman við mynstur. % merkir hér hvaða texta sem er fyrir eða eftir ull.
Vefurinn gæti fengið leitarorðið úr:
GET /search?q=ull HTTP/1.1
Host: nordurmarkadur.example
Stóra spurningin er því á hvaða hátt strengurinn ull fór úr HTTP-beiðninni og yfir í SQL-fyrirspurnina.
Þegar gögn verða að SQL-kóða
Flask-kóði sem setur leitartextann beint inn gæti litið svona út:
q = request.args.get("q", "")
sql = f"""
SELECT name, description
FROM products
WHERE visible = 1
AND name LIKE '%{q}%'
"""
rows = db.execute(sql).fetchall()
Ef q er ull verður til fyrirspurnin sem við sáum. Innihaldi q á hinn bóginn einfaldan gæsalappa gæti hann óvart lokað textastrengnum mun fyrr en forritarinn ætlaðist til:
q='
Fyrirspurnin endar þá í grófum dráttum á:
AND name LIKE '%'%'
sem er ógild SQL-setningafræði. Gagnagrunnsvilla sem stafar af gæsalappa er ætíð traust vísbending um að inntakið hafi að minnsta kosti náð inn í SQL-túlkun.
Það sannar þó ekki eitt og sér fulla SQL-innskot. Forrit getur hafnað gæsalappa sjálft, villan getur komið úr öðrum túlki eða öll óvenjuleg inntök geta valdið sama svari. Við útheimtum beinlínis samanburð sem reynir á að breyta rökfræði fyrirspurnarinnar á fyrirsjáanlegan hátt.
Stikaðar fyrirspurnir mæla gegn innskoti
Ef verkefni afhendir þennan kóða er staðan önnur:
q = request.args.get("q", "")
rows = db.execute(
"""
SELECT name, description
FROM products
WHERE visible = 1
AND name LIKE ?
""",
(f"%{q}%",),
).fetchall()
Þetta er stikuð fyrirspurn (e. parameterized query). SQL-strengurinn sjálfur og gildið (inntakið) feta hvort sína leiðina inn í gagnagrunnsviðmótið. Stakur gæsalappi í q flokkast þá án undantekninga sem lögmætur hluti leitargildisins, í stað þess að rjúfa og loka SQL-streng.
Í CTF er slíkt kóðabrot sterk vísbending gegn SQL-innskoti á þessum inntaksstað. Hér er ekki þar með sagt að önnur, leynileg og hrárri fyrirspurn leynist hvergi, en við ættum ekki að halda áfram að prófa fleiri gæsalappa á nákvæmlega sama stikaða kallinu án annarrar vísbendingar.
Fyrsta prófið: grunnlína og gæsalappi
Byrjaðu á venjulegri leit og skráðu stöðukóða, svarlengd og niðurstöður:
BASE_URL='https://slod-verkefnisins.example'
curl -i -G "$BASE_URL/search" \
--data-urlencode 'q=ull'
-G setur gögnin í fyrirspurnarstreng og --data-urlencode sér um prósentukóðun. Prófaðu síðan einn gæsalappa:
curl -i -G "$BASE_URL/search" \
--data-urlencode "q='"
Með því að beita --data-urlencode vörnum við því að skelin eða slóðaþáttunin eigi það til að flækja prófunina fyrir okkur. Í Repeater má einnig breyta q og láta Burp kóða gildið eftir þörfum.
Gættu þess að leita ekki einungis eftir orðinu „villa“. Gagnagrunnskerfi gefa ólík skilaboð:
sqlite3.OperationalError: near ...
PostgreSQL: unterminated quoted string ...
MySQL: You have an error in your SQL syntax ...
Heiti gagnagrunnskerfisins skiptir höfuðmáli í þeim tilfellum þar sem setningafræði eða innbyggð föll eru ólík milli kerfa. Fari svo að engin villa, vísbending eða atferli útheimti beinlínis að kerfið sé auðkennt, er hreinn óþarfi að þreyta langa lista af gagnagrunnssértækum föllum.
Sönn og ósönn skilyrði
Öllu sterkara próf gengur út á að búa til tvær beiðnir sem ættu einungis að frávíkja hvor frá annarri sökum sannleiksgildis skilyrðisins. Ef inntakið er inni í textastreng gæti hugmyndin verið:
' AND 1=1 --
á móti:
' AND 1=2 --
Fyrri segðin bætir við sönnu skilyrði, sú síðari ósönnu. -- byrjar athugasemd í mörgum SQL-kerfum og lætur afgang upprunalegu fyrirspurnarinnar ekki trufla nýja skilyrðið. Gættu að því að sum kerfi krefjast þess að bil komi í kjölfar -- og sömuleiðis getur samhengi gæsalappans útheimt sviga eða annars konar lokun.
Í HTTP-prófuninni er svo ráðlegt að senda bæði gildi af stað með nákvæmlega sömu kóðun og meta mismuninn:
curl -s -G "$BASE_URL/search" \
--data-urlencode "q=' AND 1=1 -- "
curl -s -G "$BASE_URL/search" \
--data-urlencode "q=' AND 1=2 -- "
Ef sanna útgáfan sýnir niðurstöður en sú ósanna engar, og munurinn endurtekur sig, er það sterk staðfesting á að skilyrðið hafi orðið hluti SQL-fyrirspurnar. Hvernig nákvæmt inntak verður úr garði gert fer auðvitað alfarið eftir því hvort notandinn er á þeirri stundu inni í gæsalöppum, við tölu, innan sviga eða inni í ORDER BY-skipun.
Villan lýsir samhenginu
Ef gagnagrunnurinn kvartar um ólokinn textastreng vantar líklega gæsalappa. Ef hann kvartar um sviga getur upprunalega fyrirspurnin haft utanumliggjandi fall eða hópun. Breyttu ekki fimm táknum í einu; notaðu villuna til að laga eina setningafræðilega forsendu.
Villumiðuð SQL-innskot
Í villumiðaðri SQL-innskot (e. error-based SQL injection) birtir þjónninn gagnagrunnsvillu sem hjálpar við greiningu eða inniheldur gögn. Byrjendaverkefni nota oft villuna til að sýna samsettu fyrirspurnina, töfluheiti eða fjölda væntra dálka.
Villuboð er þá hluti af samtalinu:
UNION query has a different number of columns
kjaftar frá um að UNION-tilraunin hafi náð alla leið til gagnagrunnsins, en að dálkafjöldinn hafi eitthvað vafist fyrir honum. Slíkt upplýsir okkur betur en hefðbundin, innihaldslaus 500-síða.
Varastu þó að álykta sem svo að sérhver 500-kóði jafngildi gagnaleka. Skoðaðu hvort villan sé stöðug og hvort breyting í inntaki gefi fyrirsjáanlega nýja villu. Í Bókasafninu eiga villurnar að vera nægilega skýrar til kennslu, en þær innihalda aðeins tilbúið skema.
UNION: bæta röðum við sýnilegt svar
SQL-aðgerðin UNION sameinar raðir úr tveimur SELECT-fyrirspurnum. Að jafnaði þurfa báðir hlutar að halda sama dálkafjölda, auk þess sem ætlast er til þess að samsvarandi gildi séu af samrýmanlegum gerðum.
Ef upprunalega fyrirspurnin skilar tveimur dálkum:
SELECT name, description FROM products ...
þarf seinni hlutinn líka að skila tveimur:
UNION SELECT 'merki-a', 'merki-b'
Í CTF má fyrst finna dálkafjöldann með litlum tilraunum:
' UNION SELECT NULL --
' UNION SELECT NULL, NULL --
Gildið NULL smellpassar við fjölmargar gagnagerðir og verður því harla þægilegt prófgildi í slíkum viðfangsefnum. Þegar réttur fjöldi skilar sér loks þannig að dálkavillan hverfur, má taka upp á því að fella ólík merki inni í dálkana:
' UNION SELECT 'AAAA', 'BBBB' --
Ef aðeins BBBB birtist á síðunni vitum við hvaða úttaksdálkur er sýnilegur. Þá þarf lokafyrirspurn aðeins að setja áhugaverð gögn í þann dálk.
Þetta er sameiningarmiðuð SQL-innskot (e. UNION-based SQL injection). Hún virkar aðeins þegar svarleiðin birtir raðir og setningafræðin leyfir sameiningu. Kjósi forritið að túlka niðurstöðuna alfarið sem já/nei-gildi er blinda aðferðin öllu líklegri til árangurs.
Að finna falda færslu án risavaxins skemalista
Í raunverulegum gagnagrunni geta verið hundruð taflna. Byrjendaverkefnið Bókasafnið gefur hins vegar töflu- og dálkavísbendingar í eðlilegri leit og afmörkuðum villum. Markmiðið er fyrst og fremst að vega og meta þær upplýsingar sem verkefnið sýnir okkur nú þegar, en ekki að reyna að keyra sjálfvirka upptalningu þvert yfir gjörvallt SQLite-skemað.
Fari svo að hefðbundin fyrirspurn velji name og description úr tiltekinni töflu sem svarið hefur bent á, og að falin færsla sé aðgreind með einhvers konar sýnileikadálki, blasir við að móta stakt afmarkað skilyrði, eða jafnvel eina UNION-fyrirspurn, sem togar út þessa tvo dálka beint úr vonu ósýnilegu röð. Notaðu merkingar og heiti úr þínu svari; kaflinn gefur ekki fulla lokafyrirspurn.
Skráðu minnstu beiðnina sem sýnir faldu færsluna. Ef ' OR 1=1 -- nægir vegna byggingar fyrirspurnarinnar er engin ástæða til að bæta við gagnagrunnssértækum kerfistöflum. Oft og tíðum er einfaldasta tilgátan, sem unnt er að styðja rökum, hin besta mögulega CTF-lausn.
Blind SQL-innskot
Stundum vill brenna við að vefurinn birti hvorki villu né niðurstöður úr sjálfri SQL-fyrirspurninni. Hann svarar kannski aðeins „fannst“ eða „fannst ekki“. Ef sönn og ósönn skilyrði breyta þeirri hegðun er um blinda SQL-innskot (e. blind SQL injection) að ræða.
Segjum að verkefnið gefi eina tilbúna secret-færslu og við megum prófa fyrsta stafinn. Hugmyndin er:
... AND substr(secret, 1, 1) = 'g'
Reynist svarið fyrir g jákvætt en neikvætt fyrir x, höfum við af því eitthvað að læra, sem svarar til einum bita af merkingarbærum upplýsingum, fyllilega án þess að gagnagrunnurinn sjálfur hafi opinberað okkur leyndarmálið af eigin hvötum.
Tímamiðuð blind innskot (e. time-based blind SQLi) reiða sig á seinkun sem mælikvarða þegar allt annað hefur brugðist. Slík föll eru að sjálfsögðu háð því hvaða gagnagrunnskerfi á í hlut, en fjöldi beiðna af þeim toga getur dunið með óþarfa álagi á litla þjónustu. Í þessari byrjendaleið notum við efnis- eða stöðukóðamun í einangruðu verkefni frekar en langar tímaleitir.
Ef ætlast er til þess að blindur útdráttur sé svarið í tilteknu CTF-verkefni, þarf að hafa í huga að slíkt verkefni þarf þá að mæla fyrir um stutt gildi og umbera afkastamikla sjálfvirkni án þess að brotna undan. Stakt sekúndubrot af handahófskenndri netafsölun jafngildir aldrei sönnun; já/nei-svar sem endurtekur sig er betri byrjun.
Gagnagrunnskerfið skiptir aðeins máli þegar setningafræðin gerir það
Gagnagrunnarnir SQLite, PostgreSQL og MySQL styðjast vissulega allir við SQL sem tungumál, en draga fram ólík föll, sínar eigin sérstöku kerfistöflur, auk ákveðinna nýjunga í útfærslu á athugasemdum. Villuboð, hausar eða afhentur frumkóði geta auðkennt kerfið. Nokkrar hagnýtar vísbendingar eru:
| Vísbending | Líklegt kerfi eða eiginleiki |
|---|---|
sqlite3.OperationalError |
SQLite |
pg_catalog eða PostgreSQL |
PostgreSQL |
information_schema |
MySQL eða PostgreSQL, eftir samhengi |
sqlite_master |
SQLite-skema |
Varastu að líta svo á að þessi tafla sé listi yfir fjórar nýjar og vonlitlar tilraunir. Gefi verkefnið glögglega til kynna að unnið sé með SQLite mætti segja þér að láta vera að prófa MySQL-athugasemdir eða PostgreSQL-seinkunarföll. Auðkenning gagnagrunnsins á að minnka leitarrýmið.
ORM útilokar ekki hráa SQL-strengi
Hlutatengdur vörpunarrammi, ORM (e. object-relational mapper), leyfir forriti að vinna með hluti í stað þess að skrifa SQL alls staðar:
products = Product.query.filter(Product.name.contains(q)).all()
Slíkt kall notar venjulega stikaða meðhöndlun fyrir q og er vísbending gegn SQL-innskoti á þeim stað. En sama forrit getur gripið til hrás SQL:
products = db.session.execute(
text(f"SELECT * FROM products WHERE name LIKE '%{q}%'")
)
Heiti á ORM-ramma er því aldrei fullgild sönnun fyrir veikleika né heldur algeru öryggi. Reyndu að rekja gaumgæfilega hvort viðkomandi notandainntak birtist á endanum sem stika eða sem bókstaflegur partur af sjálfum SQL-strengnum.
Göngudæmi: Bókasafnið
Gerðu svo vel og opnaðu leitina, veldu viðeigandi orð og staðfestu að það skili nákvæmlega einni bók. Gættu svo að því að varðveita þá beiðni örugglega í Repeater. Prófaðu næst stakan gæsalappa og lestu afmarkaða villu verkefnisins. Skráðu töfluheiti, valda dálka og hvar leitarorðið virðist lenda.
Beittu síðan klókindum og smíðaðu par sem spilar saman af sönnu og ósönnu skilyrði sem rímar vel við núverandi samhengi. Láti svörin svo af því að skiljast aðlega hefur þér vitaskuld tekist að staðfesta að innskotið sé túlkað sem slíkt, sem er eitthvað meira og annað en aðeins að kalla fram villu. Á þessum tímapunkti veistu hvort einfalt skilyrði getur látið falda röð falla í niðurstöðurnar.
Teljir þú UNION eiga betur við mælist sterklega með því að byrja með stakt NULL, bæta aðeins einum dálki við í einu og ljúka ferlinu með því að stinga tveimur auðþekkjanlegum merkjum inni í réttan dálkafjölda. Að svo búnu nægir að stíga í skjól þeirra töflu- og dálkaheita sem verkefnið hefur þegar leitt í ljós, til þess að sækja umrædda foldu færslu.
Þegar unnið er með curl tryggir --data-urlencode að hver tilraun dvelji tryggilega í einum og sama reit:
curl -i -G "$BASE_URL/search" \
--data-urlencode "q=<afmarkað SQL-inntak úr rannsókninni>"
Settu fyrir alla muni aldrei heila slóð, hlaðna af ókóðuðum &, # eða bilum, beina leið inn í skelina. # gæti orðið vefslóðarbrot og aldrei borist þjóninum, en skelin getur túlkað önnur tákn áður en curl sér þau.
Um leið og falda bókin snýr andlitinu að þér skaltu staðfesta að hún stafaði beinlínis úr SQL-svarinu og skrá í skyndi minnstu endurtakanlegu beiðnina. Lokafáninn dvelst auðvitað inni í lýsingu færslunnar; kaflinn geymir hvorki lokainntakið sjálft né gefur fánann eftir baráttulaust.
Vísbendingar gegn tilgátum og falskar jákvæðar niðurstöður
Stakur gæsalappi, sýni hann sig óbreyttan og spegilsléttan í leitarsvarinu, uppfyllir fjarri því öll skilyrði þess að flokkast sem SQL-innskot. Verði útkoman 500-kóði handa mjög langdregnum textum ber fremur að telja líklegt að um skýr stærðarmörk sé að ræða. Sönn og ósönn skilyrði sem kalla stöðugt fram nákvæmlega sama svarið mega teljast vonlaus stoð fyrir tilgátu þinni.
Afhentur frumkóði með stikaðri fyrirspurn, stöðug almenn villumeðferð og enginn merkingarbær munur eftir rökræn skilyrði vísa skýrt og ákveðið til þess að leitast skuli við að finna annan heppilegan inntaksstað. Óvandaður bannlisti sem heggur út orðið UNION lokar vitanlega engan veginn fyrir hvers kyns SQL-innskot; hann staðfestir fremur að gripið hafi verið í taumana gagnvart einmitt þessari tilteknu framsetningu.
Markmiðið er öðruvísi en margir ætla; það beinist ekki að því að pína þjóninn til að láta dæluna ganga. Eina markmiðið er að sýna mælanlega fram á hvernig notandastýrt gildi umturnar SQL-merkingu á fyrirsjáanlegan hátt, og skáka þannig afmörkuðu CTF-markmiði.
Samantekt
Skaðlegt SQL-innskot raungerist þegar gögn ryðjast yfir vörðuð mörk og stökkbreytast upp úr þurru í setningafræðilegan SQL-kóða. Við byrjum á venjulegu svari, notum gæsalappa til að kanna samhengi og staðfestum tilgátuna með sönnu og ósönnu skilyrði. Villumiðuð innskot (e. error-based SQLi) reiða sig mikið á gagnleg villuboð, aðgerðin UNION fær hiklaust um að bæta eigin röðum ofan í sýnilegt svar, meðan blind innskot leita hófanna með því að mæla blæbrigðamun algjörlega án þess að gögnin njóti sýnileika.
Vel stikuð fyrirspurn gildir ætíð sem sterk vísbending um öryggi og ræður frekar afleitilega með veikleikum á slíkum inntaksstað. Þá má ekki gleymast að eitt venjulegt ORM-forrit kann engu að síður að lauma með óvarinni, hrárri strengjasamsetningu í bakhöndinni. Við auðkennum gagnagrunnskerfið aðeins þegar það minnkar leitarrýmið og notum minnstu mögulegu fyrirspurn til að ná fánanum úr tilbúnu umhverfi.
Í næsta kafla fylgjum við notandatexta yfir önnur túlkunarmörk: frá vefþjóni yfir í stýrikerfisskipun eða skel.