  ̌      ̌    P 48 (2020/2021) 6 23 Brez gesla in brez omejitev s prelivom spomina M K Uvod Ko se poglobimo v računalništvo, spoznamo, da težo modernega sveta držijo le desetletja stara koda, le- pilni trak in upanje. To negotovost najbolje prika- žejo varnostne luknje v sistemih, ki so razširjeni po celem svetu. Primer take ranljivosti je Heartbleed, ki je bil zaznan leta 2014. Heartbleed je omogočal javen vpogled v sisteme z ranljivo verzijo knjižnice OpenSSL, ki jo uporabljamo za vzpostavljanje varne povezave do strežnikov. Prvo ranljivo verzijo so iz- dali leta 2012, do odkritja napake pa je bila ta priso- tna že v več kot dveh tretjinah vseh spletnih strežni- kov. V tem članku se bomo poglobili v napako (poime- novano Baron Samedit), ki so jo januarja letos odkrili na sistemih Linux in macOS. Ranljivost so našli v pro- gramu sudo in pokazali, da lahko dobi vsak uporab- nik administratorske (root) pravice na sistemu, ne da bi imel za to pravice in brez poznavanja kakršnih koli gesel. Tako Heartbleed kot Baron Sameedit sta ranljivo- sti, povzročeni s prekomernim in neželenim dosto- pom do pomnilnika. Takšne vrste napak so med najbolj pogostimi in so tipične za programe, napi- sane v jezikih C/C++. Da bomo lahko razumeli, za- kaj pride do takih ranljivost in zakaj lahko ostanejo tako dolgo skrite, moramo najprej razumeti, kako programi uporabljajo spomin. Model računalniškega pomnilnika Računalniški pomnilnik (angl. Random Access Me- mory – RAM) opravlja pomembno vlogo pri delova- nju računalnika. Vsakič, ko zaženemo katerikoli pro- gram, mu operacijski sistem dodeli del pomnilnika in vsak bajt spomina označi z unikatnim naslovom. Na 32-bitnih sistemih je vsak naslov sestavljen iz 32 bitov. Ko jih zapišemo v šestnajstiškem sistemu, se- gajo od 0x00000000 do 0xFFFFFFFF. Pomnilnik je razdeljen na predele, ki opravljajo različne naloge (slika 1). Ob zagonu programa se v najmanjše naslove pomnilnika naloži koda zagna- nega programa. Poleg je prostor za vse statično defi- nirane spremenljivke v našem programu. Na drugem koncu pomnilnika so programu na voljo knjižnice operacijskega sistema, ki jih uporablja za dostop do povezanih naprav, kot so ekran, miška, tiskalnik ali trdi disk. Med dvema skrajnostma pomnilnika se nahajata dve vrsti dinamičnega spomina: kopica in sklad. Program Statine sprem. Kopica Sklad Oper. sistem 0x00000000 0xFFFFFFFF SLIKA 1. Model računalniškega pomnilnika ob delovanju programa Sklad se nahaja pri višjih naslovih pomnilnika. V njem so shranjeni trenutno aktivni klici funkcij ter lokalne spremenljivke. Sklad deluje hitro, vendar je njegova velikost omejena na nekaj megabajtov (točna omejitev je odvisna od operacijskega sistema). Vse večje objekte mora program posledično shraniti v kopico. Kopica se nahaja pri manjših naslovih in raste proti večjim. Velikost mu omejuje le skupno število naslovov, ki jih ima program na voljo, ter ko- ličina prostega spomina v računalniku.   ̌      ̌    P 48 (2020/2021) 624 Preprost primer ranljivosti Kako zgleda ranljiv program v praksi? V tem delu bomo pregledali konkretni primer napake. Spodaj je program, napisan v jeziku C++ in skriva kritično napako. Jo opazimo? #include #include const char* const GESLO = "geslo123"; int main() { int geslo_je_pravilno = 0; char prebrano_geslo[100]; printf("Vpiši geslo: "); gets(prebrano_geslo); if (strcmp(prebrano_geslo, GESLO)) { printf("Napačno geslo.\n"); } else { printf("Pravilno geslo.\n"); geslo_je_pravilno = 1; } if (geslo_je_pravilno) { // Uporabnik se je uspešno prijavil printf("Dobrodošli v vaš račun.\n"); } return 0; } Preden poskušamo razumeti napako, se posvetimo delovanju programa. V prvih dveh vrsticah z uvo- zom knjižnic poskrbimo, da bomo lahko brali iz stan- dardnega vhoda, pisali na standardni izhod in pri- merjali nize znakov. Naša prva spremenljivka, GESLO, je enaka »geslo123« in predstavlja skrivno ge- slo, ki ga uporabnik potrebuje za vstop v svoj račun. Program se začne izvajati na peti vrstici s funkcijo main(). Najprej si pripravimo spremenljivko, ki nam bo povedala, ali je to geslo pravilno, in jo nastavimo na ničelno vrednost. Potem rezerviramo 100 bajtov prostora za geslo, ki ga bo vpisal uporabnik. Obe spremenljivki se nahajata na skladu ena za drugo. Uporabnika prosimo, da vnese geslo, ga prebere- mo in shranimo v prebrano_geslo. Naslednji blok kode primerja prebrano geslo in GESLO ter obvesti uporabnika, ali je vneseno geslo pravilno. Če je ge- slo pravilno, si to zabeležimo v geslo_je_pravilno. Če se je uporabnik uspešno vpisal, ga na koncu poz- dravimo v njegovem računu. V primeru resničnega programa bi imeli uporabniki tu dostop do podatkov ali funkcionalnosti, ki je namenjena le njim. Kako izvedba programa zgleda v praksi? Poglejmo si dva primera: Vpiši geslo: geslo123 Vpiši geslo: 123456 Pravilno geslo. Napačno geslo. Dobrodošli v vaš račun. PRIMER. Levo: uspešen vpis v sistem. Desno: neuspešen vpis v sistem. Vidimo, da program očitno deluje. Kje je torej te- žava? Poglejmo, kaj se zgodi, če poskusimo vnesti daljše geslo: Vpiši geslo: iiiiiiiiiiiiiiiiiiiiiiiiii iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii Napačno geslo. Dobrodošli v vaš račun. [1] 692249 segmentation fault Zanimivo. Program zazna, da je geslo napačno, vendar nas na koncu vseeno spusti v račun, preden se sesuje z napako segmentation fault. Kako je to mogoče? Kaj se je zgodilo? Problem je v delovanju funkcije gets. Ta se na- mreč ne meni za to, koliko spomina ima na voljo, ampak veselo napiše vse dobljene znake v spomin, četudi to pomeni, da ob tem prepiše druge spremen- ljivke. Kot smo že omenili, je spremenljivka prebrano_geslo shranjena na skladu. Tabela 1 pri- kazuje strukturo spomina v treh primerih. Zanima nas predvsem tretji primer, kjer smo z be- sedilom prepisali število geslo_je_pravilno. Pona- vljajoče zapisan bajt je 011010012. Kot znak se to prevede v ‘i’, kot število pa v 105. Vrednost spremen- ljivke geslo_je_pravilno je tako neničelna, kar nam dovoli vstop v račun. Bolj natančno je vrednost štirikrat ponovljena vrednost 105 v binarnem siste-   ̌      ̌    P 48 (2020/2021) 6 25 prebrano_geslo geslo_je_pravilno ... ‘1’ ‘2’ ‘3’ ‘4’ ‘5’ ‘6’ ... 0 0 0 0 ... ... ‘g’ ‘e’ ‘s’ ‘l’ ‘o’ ‘1’ ‘2’ ‘3’ ... 1 0 0 0 ... ... ‘i’ ‘i’ ‘i’ ‘i’ ‘i’ ‘i’ ‘i’ ‘i’ ‘i’ ‘i’ ... ‘i’ 105 105 105 105 ... TABELA 1. Vsaka celica tabele predstavlja en bajt spomina v skladu. Na levi je spomin z manjšim naslovom, na desni pa z večjim. Tri vrstice ponazarjajo tri razlǐcne primere, ki smo jih poizkusili. Vidimo, da je v tretjem primeru vneseno besedilo preseglo 100 bajtov, dodeljenih spremenljivki prebrano_geslo, in se prelilo v naslednje celice spomina. mu, kar je enako 01101001 01101001 01101001 011010012 “ 105`105¨28`105¨216`105¨224 “ 1768515945 . Zaradi prekomerne količine i-jev se ti nadaljujejo še naprej po spominu. Za potrebe našega razumevanja je pomembno, da nekoč naletijo na vrnitveni naslov funkcije (return address). To je spremenljivka, v ka- teri je shranjen naslov spomina, kjer bo program na- daljeval izvajanje, ko se trenutna funkcija zaključi. Ker smo ta naslov prepisali z 0x69696969 (105 v šestnajstiškem sistemu je 6916), je program poskusil dostopati do neveljavnega spomina, kar je povzro- čilo napako segmentation fault. Iz tega smo se naučili, da ni potrebno pretiravati z dolžino gesla, ki ga vpišemo. Če je geslo daljše od 100 znakov, vendar ne predolgo, lahko dobimo dostop do sistema, ne da bi se ta sesul. Takšna previdnost podcenjuje dejstvo, da imamo dostop do vrnitvenega naslova in do razmeroma ve- likega dela spomina, ki ga lahko prepišemo po svoji volji. Vrnitveni naslov bi lahko spremenili tako, da bi bil usmerjen v spomin znotraj spremenljivke prebrano_geslo. To pomeni, da bi računalnik za- čel interpretirati bajte kot ukaze programa. V našem primeru bi ponavljal ukaz 0x69, kar je na 32-bitnih Intel procesorjih ukaz za množenje. V principu lahko v ta del spomina napišemo, kakr- šenkoli program si zaželimo. Potrebno je samo, da je program manjši od 100 bajtov in da se lahko izvede v takih okoliščinah. Najbolj zanimiv program, ki ga lahko zaženemo, je ukazna vrstica (shell), saj lahko z njeno pomočjo zaženemo poljubne druge programe. Točna priprava in umestitev programa presega na- men tega članka. Več učnih virov na to temo pa lahko najdete na spletu. Ranljivi Baron Samedit Ukaz sudo (angl. SuperUser DO) dovoli izbranim upo- rabnikom sistema, da zaženejo druge programe s pravicami superuporabnika. Podobno kot administ- rator na sistemu Windows ima superuporabnik ne- omejeno moč na sistemu. Namesti lahko nove pro- grame, prebere vse datoteke na sistemu, tudi tiste, ki so last drugih uporabnikov. Če želi, lahko iz računal- nika celo izbriše operacijski sistem. Superuporabnik je vsemogočen. Torej si lahko mislite, kako nevarno bi bilo, če bi imeli do tega ukaza nenadzorovan dostop vsi upo- rabniki. Prav takšno ranljivost so letos odkrili razi- skovalci pri Qualys Research Labs. Vsak z dostopom do uporabniškega računa na sistemu lahko dobi pra- vice superuporabnika. Tudi če ta račun sicer nima dovoljenja za uporabo sudo, ali če uporabnik ne po- zna gesla računa, ki ga upravlja. Najbolj presene- tljivo je, da je ta napaka v sudo prisotna že od julija 2011. Dejansko napako so našli pri uporabi ukaza sudo- edit, ki je točna kopija ali celo povezava programa sudo. Ukaz je namenjen urejanju tekstovnih dato- tek s pravicami superuporabnika. Sudoedit avtoma- tično odpre urejevalnik besedila in v njem datoteko, ki jo hoče uporabnik urediti. Pred tem program pre- veri, da ima uporabniški račun dovoljenje za upo- rabo sudo, in zahteva, da vnese uporabnik geslo ra- čuna, ki ga uporablja. Ranljivost je prisotna pri uporabi argumenta -s. V primeru, da se njegov argument konča s poševnico (\), bo prekoračil meje svoje spremenljivke in začel nenadzorovano pisati po spominu. Za razliko od na- šega primera v prejšnjem odseku se vse to dogaja v kopici, ne v skladu. Če vas zanima, ali je vaš sistem ranljiv, lahko poizkusite pognati spodnji ukaz           P 48 (2020/2021) 626 sudoedit -s ‘\’ ‘To besedilo se bo prelilo po spominu’ Če dobite napako Segmentation Fault, je vaš sis- tem še vedno ranljiv in ga morate posodobiti. V na- sprotnem primeru bi se vam morala na zaslon izpisati navodila za uporabo, začenši z »usage: sudoedit«. Ker je napaka prisotna v ko- pici in ne v skladu, nam izkoriščanje te ranljivosti preprečuje še randomizacija spomina (angl. Address space layout randomization – ASLR). Naloga ASLR je, da naključno spreminja, na katerem naslovu pomnil- nika se nahaja kakšna spremenljivka. S tem zašči- timo programe pred napadi, za katere je potreben natančen dostop do spremenljivk. V tem primeru so problem rešili tako, da so na- pad pognali 128 tisočkrat na različnih naslovih. Ker ranljivost preverijo hitro in je možnosti razmeroma malo, lahko raziskovalci dobijo neomejen dostop do sistema v manj kot 20-ih sekundah. Po tem, ko so raziskovalci našli ranljivost, so 13. januarja o tem skrivaj obvestili razvijalce programa sudo. Nato so 19. januarja poslali popravek vzdrževalcem vseh ve- čjih distribucij Linuxa, ki so teden dni kasneje istoča- sno izdali popravljene verzije. Pri takšni ranljivosti je zelo pomembno, da ni prehitro javno razkrita. Z etičnim razkritjem kritičnih napak raziskovalci po- skrbijo, da lahko vsak zaščiti svoje naprave, preden navodila za napad pristanejo v rokah javnosti. Več podrobnosti o raziskavi in ranljivosti si lahko preberete na njihovi spletni strani blog.qualys.com. Zaključek Naslednjič, ko boste programirali, raje dvakrat pre- verite, ali ima vaš sistem trdne temelje in ali upora- bljate ranljive funkcije. Uporaba funkcije gets nam na primer v novejših prevajalnikih kode prikaže opo- zorilo, da njena uporaba ni varna. Od verzije C++14 naprej pa gets sploh ni več na voljo. Varnost progra- mov in programskih jezikov se izboljšuje, vendar so jim varnostni raziskovalci vselej za petami, da naj- dejo vse, še tako skrite, napake. Če vas ta članek nauči le eno stvar, naj bo to: re- dno in skrbno posodabljajte svoje računalnike, še po- sebej sisteme, do katerih ima dostop veliko ljudi. ˆ ˆ ˆ Križne vsote Naloga reševalca je, da izpolni bele kvadratke s števkami od 1 do 9 tako, da bo vsota števk v za- porednih belih kvadratkih po vrsticah in po stolpcih enaka številu, ki je zapisano v sivem kvadratku na za- četku vrstice (stolpca) nad (pod) diagonalo. Pri tem morajo biti vse števke v posamezni vrstici (stolpcu) različne. 9 17 8 3 14 13 9 21 8 12 11 14 12 17 ̌ ̌  917 8 53 314 13 49 921 8 26 12 57 11 14 518 12 237 17 89 ˆ ˆ ˆ