Skipanainnskot
Vefþjónn vinnur alls ekki öll verk sín upp á eigin spýtur. Honum er til að mynda í lófa lagið að ræsa utanaðkomandi myndvinnsluforrit, þjöppunartól, PDF-breyti eða jafnvel hefðbundnar kerfisskipanir og styðjast síðan við úttakið við gerð HTTP-svars. Sú atburðarás kallar á að notandastýrð gögn ferðist yfir ný og oft varasöm mörk:
HTTP-inntak → vefþjónn → ferli eða skel → forrit → úttak
Ef vefþjónn setur inntakið í textastreng sem skel túlkar getur textinn hætt að vera eitt viðfang og orðið að nýrri skipun. Það er skipanainnskot (e. command injection).
Verkefnið Ping og pong sviðsetur nokkurs konar „ping“-þjónustu, þó fyllilega án þess að senda raunverulega netpakka út á opið net. Það kallar staðbundið lab-ping-hjálparforrit, en gerir það í gegnum skel með notandatexta inni í skipanastreng. Þegar okkur hefur tekist að staðfesta að túlkunin fari raunverulega fram er okkur heimilt að knýja fram keyrslu á öðru tilbúnu hjálparforriti sem sviptir hulunni af lokafánanum.
Aðeins einangraða verkefnið
Skaðleg skipanainnskot geta ef til vill leitt af sér opna og almenna keyrslu skipana. Prófaðu hana aðeins í þessu Örvangursverkefni eða eigin einangruðu umhverfi. Notaðu aðeins skaðlaus prent- og biðpróf og hjálparskipunina sem verkefnið nefnir. Ekki senda netbeiðnir, opna gagnvirka skel, eyða skrám eða kanna hýsikerfið.
Að ræsa forrit jafngildir alls ekki því að ræsa skel
Stýrikerfi getur ræst forrit beint með viðfangalista (e. argument vector). Python-kóði gæti verið:
subprocess.run(
["ping", "-c", "1", host],
capture_output=True,
text=True,
)
Í framangreindu dæmi samanstendur listinn af fjórum stökum. Gildið host varðveitir því alltaf stöðu sína sem eitt stakt viðfang fyrir forritið ping, alveg óháð því þó að það innihaldi ef til vill semíkommu. Þar kemur alls engin skel við sögu sem fær tækifæri til að túlka textann frekar.
Oft og tíðum stíga forritarar hins vegar það óheillaspor að púsla saman einum löngum streng og biðla svo beinlínis til skeljarinnar um að lesa hann yfir:
subprocess.run(
f"ping -c 1 {host}",
shell=True,
capture_output=True,
text=True,
)
Ef host er 127.0.0.1 fær skelin:
ping -c 1 127.0.0.1
Ef host er:
127.0.0.1; printf GGFI_MARKER
fær skelin:
ping -c 1 127.0.0.1; printf GGFI_MARKER
Í þessu samhengi gildir semíkomman sem dæmigerður skeljarstafur (e. shell metacharacter) og hefur það stakt hlutverk að binda enda á fyrri skipunina og blása lífi í þá næstu. Úttakið úr printf getur þar af leiðandi birst hnökralaust beint í kjölfar ping-úttaksins.
Rétt smíðaður viðfangalisti verkar í raun sem skotheld vísbending um að við eigum ekki við skeljarinnskot að etja á þessum tiltekna stað. Forritið sem er ræst getur samt túlkað eigið viðfang sem valkost, skráarheiti eða annað skipanamál. Það kallast oft viðfangainnskot (e. argument injection) og er önnur tilgáta. Semíkomma sem fer sem bókstaflegt viðfang sannar ekki skipanainnskoti.
Hvernig bregst skelin við strengnum?
Skelin tekur ekki við strengnum líkt og um væri að ræða venjulegan texta úr bók. Hennar hlutverk er öllu frekar að hluta hann sundur í skipanir, viðföng og viðeigandi stýriaðgerðir. Nokkur tákn sem sjást oft í Linux-CTF eru:
| Tákn | Merking í dæmigerðri skel |
|---|---|
; eða línuskipti |
Keyra næstu skipun á eftir þeirri fyrri |
&& |
Keyra næstu skipun aðeins ef fyrri tókst |
|| |
Keyra næstu skipun aðeins ef fyrri mistókst |
| |
Senda staðalúttak fyrri skipunar í þá næstu |
> og < |
Beina úttaki eða inntaki |
$(...) |
Keyra skipun og setja úttak hennar inn í streng |
'...' og "..." |
Hafa áhrif á hvaða tákn eru túlkuð |
\ |
Breyta merkingu næsta tákns í mörgum samhengi |
Þessi listi á alls ekki að þjóna sem gátlisti til að prófa í heild sinni. Okkar fyrsta og helsta verk er að leita uppi hið eiginlega samhengi strengsins. Ef frumkóði sýnir shell=True og inntakið er ótilvitnað er einfaldur aðskiljari eðlileg tilgáta. Ef inntakið er inni í einföldum gæsalöppum þarf að skilja hvar þær byrja og enda áður en einhver tilraun er valin.
Þessu til viðbótar ber að hafa hugfast að skeljar eru æði ólíkar. Skeljarnar Bash, dash, PowerShell og cmd.exe styðjast til að mynda engan veginn við sömu setningafræðina. Þar sem Ping og pong er afar afmarkað og sérsniðið Linux-verkefni; mega villuboð og afhentur frumkóði vel duga til að slá því föstu um hvaða samhengi er að ræða.
Byrjum á venjulegri beiðni
Grunnbeiðni verkefnisins gæti verið:
GET /ping?host=127.0.0.1 HTTP/1.1
Host: nordurmarkadur.example
Svarið sýnir úttak staðbundna hjálparforritsins:
HTTP/1.1 200 OK
Content-Type: text/plain
PONG 127.0.0.1
Tryggðu að þessi grunnbeiðni varðveitist inni í Repeater. Að því búnu er ráðlagt að prófa sig áfram með mjög afmarkað, og öruggt merki (e. marker), sem mun reynast einkar auðvelt að aðgreina frá hinu venjulega svari:
GGFI_MARKER_42
Með curl skaltu láta --data-urlencode kóða allt host-gildið:
BASE_URL='https://slod-verkefnisins.example'
curl -i -G "$BASE_URL/ping" \
--data-urlencode 'host=127.0.0.1; printf GGFI_MARKER_42'
Birtist merkið með skýrum hætti í svarinu hefur notandainntakið sannanlega farið aðra og dekkri leið en að verka sem aðeins bókstaflegt hýsisnafn. Skelinni hefur á einhvern hátt verið talin trú um að keyra þarna sjálfstætt, en sem betur fer skaðlaust, forrit. Slíkt telst til mun sterkari staðfestingar en almenn gagnagrunns- eða kerfisvilla sem annars kynni að hljótast af notkun semíkommu.
Af hverju --data-urlencode?
& skilur fyrirspurnarreiti að, # byrjar brot sem vafrinn sendir ekki og bil þarf kóðun. Séu skeljarstafir aftur á móti settir algjörlega hráir og ókóðaðir inn í slóð, er voðinn vís, því hvort sem er vafrinn sjálfur, skelin sem á í hlut eða jafnvel nærliggjandi HTTP-þáttari kunna þá að eiga það til að frukka eitthvað í þeim og umskrifa áður en þeir skila sér raunverulega alla leið. Vandaðu því til verka; kóðaðu vissulega reitinn sjálfan vel og vandlega en forðastu þó að rugla merkingu sjálfrar prófunarinnar.
Staðalúttak, staðalvilla og skilagildi
Ferli hefur staðalúttak og staðalvillu. Vefþjónn getur birt annað, bæði eða hvorugt. printf skrifar á staðalúttak, en skipun sem finnst ekki skrifar villu oft á staðalvillu.
Kóði gæti sameinað bæði:
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
)
return result.stdout + result.stderr
Suma vefi hentar auðvitað að birta einvörðungu stdout-strauminn og leyna faglega öllum hrakföllum fyrir notandanum. Sé svo komið, er auðvelt að leiðast í þá gildru að misheppnuð tilraun líti hreinlega út fyrir að vera algjörlega tómt svar, enda þótt skelin hafi engu að síður sannarlega ræst af stað.
Skilagildi (e. exit status) er tala sem ferlið skilar. Núll merkir venjulega árangur. && og || byggja á þessu gildi, svo niðurstaða upphaflegu skipunarinnar getur ráðið hvort seinni skipun keyrir. ; reynir hins vegar næstu skipun óháð skilagildi þeirrar fyrri.
Ef host=ekki-til veldur því að lab-ping skilar villu gæti && printf MARKER aldrei keyrt en ; printf MARKER gert það. Hér er vitanlega aðeins um að ræða setningafræðilega skýringu en engan veginn neitt formlegt merki um að vefurinn leggi sig fram um að sía út sjálft orðið printf.
Blind skipanainnskot
Í blindri skipanainnskoti (e. blind command injection) skilar vefurinn ekki skipanaúttakinu. Hann gæti alltaf svarað:
Beiðni móttekin.
Við slíkar kringumstæður útheimtum við að sjálfsögðu annars konar, afmarkað og áþreifanlegt merki um að keyrsla hafi farið fram. Inni í staðbundnu eða sérsmíðuðu verkefni eins og þessu má hæglega notast við stuttbrotna biðtíma (e. sleep):
127.0.0.1; sleep 1
Bæði er ljúft og skylt að bera saman nokkrar vel heppnaðar grunnbeiðnir við tilraunir sem fela í sér vísvitandi eina eða tveggja sekúndna tafir. Sýni mælingin fram á stöðuga og fyrirsjáanlega heillar sekúndu seinkun í sérhvert sinn, rennir það sterkum stoðum undir tilgátuna. Einstök stök beiðni sem mælist ef til vill 900 ms á eftir áætlun á hins vegar sáraeinfalda skýringu og getur stafað af venjulegri netsveiflu.
Af fremsta megni ber okkur að forðast óþarflega langar biðtímaskipanir eða vélræna keyrslu margra samhliða beiðna. Þær hægja á þjónustunni fyrir aðra og bæta litlu við sönnunina. Stundum vill heppnin þó vera með okkur; bjóði verkefnið nefnilega upp á örugga og staðbundna úttaksleið — eins og að fella niður fastmótað merki í tilbúna tímabundna skrá sem einhver önnur API-leið nýtir sér svo að lesa — getur slík aðferð alloft staðið sem ótrúlega miklu öflugri og skýrari vitnisburður en einhver afstæð og kviklynd tímamæling.
Svokölluð utanbandsstaðfesting (e. out-of-band interaction) snýst aftur á móti um að fá sjálfan þjóninn til þess að varpa af stað DNS- eða jafnvel HTTP-beiðni og beina henni þá allt annars staðar en til okkar. Hún á ekki heima í þessu byrjendaverkefni: hún krefst ytri móttakara og útleiðar sem við viljum ekki opna. Ping og pong birtir staðalúttak beint og þarf því enga slíka leið.
Bannlistar segja einungis til um hvað verið hefur bannað
Algengt er að CTF-þjónar hirði markvisst til hliðar semíkommur eða einfaldlega orðið cat. Sú staðreynd segir okkur tæpast annað en að viðkomandi textabútur hljóti ákveðna sérstaka meðhöndlun; hún sýnir hins vegar alls ekki að skelin sé á neinn hátt hætt að sinna túlkunarhlutverki sínu. Skel hefur fleiri aðskiljara, línuskipti, pípur, tilvitnanir og skipanastaðgöngu. Þess vegna getur bannlisti orðið að vísbendingu um að skoða nákvæma þáttun fremur en lokaðri niðurstöðu.
Við látum þó ekki glepjast svo glatt og dembum okkur tæpast beint í að prófa einhvern ógnarstóran hjáleiðalista. Sé hinni hefðbundnu semíkommu ; ýtt burt skaltu fyrst leggja spurningarnar fyrir þig:
- Skilar inntakið sér enn inn í skelina í ljósi tiltæks frumkóða, eða þá staðfestra mælinga okkar?
- Gefur líkurnar okkur vísbendingu um hvort upphaflega skipuninni sé lífs auðið að heppnast eður ei?
- Hvaða nákvæmlega eina aðskiljarategund gæti tekið við keflinu og reynst sniðin að þessari nýju niðurstöðu og auðvitað sjálfri skelinni sem verkefnið spilar í?
Prófaðu síðan eitt skaðlaust prentmerki. Líti engin raunverulega merkingarbær og afdrifarík breyting dagsins ljós að afloknum nokkrum vel grunduðum tilraunum, hlýtur það að teljast bæði heiðarlegt og skynsamlegt að setjast niður og endurmeta tilgátuna frá grunni.
Meðhöndlun sem bætir lausnarstöfum við texta fyrir skel (e. shell escaping) getur líka breytt inntaki áður en það fer í strenginn. Villuboð eða afrit af lokaskipuninni geta sýnt hvort gæsalöppum eða bakskástrokum var bætt við. Rétt byggður viðfangalisti er sterkari vísbending gegn veikleika en sérsmíðaður bannlisti, því skelin fær þá ekki strenginn til túlkunar.
Kóðavísbendingar
Ef rýnt er í Python-kóða blasa eftirfarandi oftar en ekki við sem ótvíræð merki þess að von sé á skeljartúlkun:
os.system(f"tool {user_input}")
subprocess.run(f"tool {user_input}", shell=True)
subprocess.Popen("tool " + user_input, shell=True)
Tökum við önnur forritunarmál til hliðsjónar eru sambærileg föll gjarnan kölluð fram á sjónarsviðið, til að mynda föll á borð við exec, system, hin klassísku bakhök sem oftar en ekki leynast í eldri PHP-kóða eða jafnvel önnur forritaskil API sem hreinlega innbyrða einn fágaðan skipanastreng í heild. Heiti fallsins eitt nægir þó ekki; sum API hafa bæði strengja- og viðfangalistaútgáfur.
Þessi Python-kóði notar aftur á móti lista:
subprocess.run(["tool", user_input], shell=False)
Skeljatákn í user_input verða þá venjulega bókstafleg gögn fyrir tool. Ef tool sjálft hefur hættulegan valkost eða túlkar eigið skipanamál getur önnur tegund innskots verið möguleg, en ; id er ekki sjálfkrafa keyrt af skel.
Eiginlegri lausnarkynningu í gagnaglímu ber í reynd skylda til að útlista og nefna þessi vörðuðu mörk nákvæmlega: „host var sett ótilvitnað í streng sem /bin/sh túlkaði vegna shell=True“ er betri skýring en „vefurinn keyrir Python“.
Göngudæmi: Ping og pong
Gerðu svo vel að opna verkefnið og fleygðu af stað venjubundnu hýsigildi. Taktu þér góðan tíma til að skrásetja af nostursemi hvort tveggja; beiðnina sjálfa auk hins vísa PONG-svars sem í kjölfarið ríður. Því næst læturðu eina knappa beiðni líta dagsins ljós og reynir á að fella saman bæði einn einfaldan aðskiljara við og eitt stakt printf-merki til frekari tryggingar. Ef merkið birtist skaltu varðveita þá beiðni sem staðfestingu.
Lýsing verkefnisins lætur að því liggja að þarna sé á kreiki sérstök, staðbundin hjálparskipun er þjóni hinu eina og göfuga hlutverki: að stíga fram í ljósið og draga á eftir sér lokafánann. Taktu nú prentprófið fínlega út af borðinu og tylltu einmitt þessari mikilvægu hjálparskipun inn í staðinn — hyggðu þó vel að því að raska á engan hátt nokkru öðru innviði:
curl -i -G "$BASE_URL/ping" \
--data-urlencode 'host=127.0.0.1<staðfestur aðskiljari> /usr/local/bin/show-flag'
Í framsetningunni okkar hér gegna hornklofarnir einfaldlega hlutverki staðgengla og munu tákna þann eina rækilega gagnlega aðskiljara er prentprófinu sjálfu auðnaðist að staðfesta með góðum árangri. Kafli þessi mælir fram hvorki fullmótaða lokabeiðni né skvettir hann upp fánanum í heimildarleysi.
Skili svarið þér einungis tómlegu PONG ber þér að staldra við og leggja kapp á að kryfja hvort úttakið af skipuninni hljóti sennilega fylgsni sitt inni í sjálfum stderr-straumnum; þá jafnframt hvort inntakið kunni óafvitandi að hafa villst langleiðina inn fyrir ógagnsæjar gæsalappir og ekki hvað síst – mæla hvort og þá hvernig kóðunin stóð rækilegan vörð um afgerandi hlutverk aðskiljarans allt til enda. Ekki skipta samtímis yfir í pípu, skipanastaðgöngu og nýja slóð.
Ástæða þess að þjónustunni var beinlínis varnað útgöngu og henni markaður einangraður stakkur, liggur vafalaust í þeirri einföldu og ófrávíkjanlegu staðreynd að skelinni reynist full fært að túlka almennar kerfisskipanir að frjálsum vild. Hafðu endilega á hreinu að sjálfri einangruninni, strangt til tekið, var í engum skilningi ætlað að standa sem þrándur í götu eða glímuþraut til þess að buga; henni mátt þú fyllilega láta í friði og snúa þér frekar að endanlegu markmiði sem hefur endanlega klárað hlutverk sitt þegar okkur auðnast að heilla fánann loks upp á yfirborðið með tilstilli hjálparskriftarinnar.
Vísbendingar gegn tilgátum og falskar jákvæðar niðurstöður
Standi semíkomma hressilega óáreitt og prýði textann líkt og við sjáum í PONG 127.0.0.1; id – þá vottar slíkt augljóslega fyrir því að tiltekið hjálparforrit hafi gleymt sér og kokgleypt framlagðan textastreng sem einn órofa pakka, einitt skráðan sem eitt og stakt viðfang. Lítið mál væri að slá því upp að hlutir slíks eðlis ávænir sjaldnast um skaðlegt skipanainnskot. Almenn 400-villa frá inntakssannprófun sannar ekki að skel hafi séð textann.
Afhentur frumkóði með föstum keyranlegum forritsstreng, shell=False og aðskildum viðfangalista mælir gegn skeljarinnskoti. Stöðugt svar sem hvorki breytist með sönnu skilyrði, prentmerki né stuttri afmarkaðri bið styður hana ekki heldur.
Aftur á móti hrópar það á þig stórum stöfum ef lokaskipunni verður varpað hreint og beint inn í grimmileg villuskilaboð; slíkt eitt og sér getur talist afbragðs sönnunargagn. Sömuleiðis horfum við björtum augum til þess hvort kvaðningin shell=True blasi við okkur sjónum ljósari af blöðum handritsins eða hreinlega framkalli furðulegt, óþekkt en þó merkingarbært úttaksmerki í svörunum. Við veljum næsta skref eftir slíkum gögnum, ekki vegna þess að slóðin heiti /ping.
Samantekt
Að ræsa forrit með viðfangalista er annað en að senda textastreng í skel. Þegar notandainntak lendir í skeljarstreng geta aðskiljarar, pípur, tilvitnanir og skipanastaðganga breytt gögnunum í nýja skipun.
Ef þú, lesandi góður, tekur gagnaglímu af alvöru munt þú fyrst senda varfærnislega grunnbeiðni út af borðinu og að svo búnu láta sverfa til stáls og freista þess rækilega að sanna endanlega túlkun; eitthvað sem gjarnan fer fram með framlagi einstaks en bráðgagnlegs prentmerkis — án þess þó að þú freistist til að velta fleiri en einum hlut eða breytu á hliðina í einu og sömu andránni. Blind tilvik má staðfesta með stuttri bið í einangruðu verkefni, en við forðumst langar seinkanir og ytri netbeiðnir. shell=True, os.system og strengjasamsetning eru sterkar kóðavísbendingar; viðfangalisti og bókstafleg meðhöndlun skeljatákna mæla gegn þessari tilgátu.
Í næsta kafla skoðum við annan túlk: sniðmátsvél. Þar getur notandatexti orðið að sniðmátskóða inni í vefþjóninum, jafnvel þótt engin skel sjáist í upphaflega kóðanum.