Objavljeno: 26.8.2008 14:42 | Avtor: Edi Strosar | Monitor Julij-avgust 2008

Linux in varnost

Linux. Tema žgočih razprav, če ne kar pravih besednih spopadov med ljubitelji tega operacijskega sistema in njihovih nasprotnikov. Prvi opevajo njegove pozitivne lastnosti, in teh je res veliko, drugi poudarjajo njegove pomanjkljivosti. Seveda se ne bomo spuščali v banalno polemiziranje, kateri OS je (naj)boljši. O okusih je namreč nemogoče razpravljati. Nekoliko pobliže si bomo ogledali Linux s stališča varnosti in predstavili nekatere pomanjkljivosti.

Večina uporabnikov Linuxa je sveto prepričana, da je njihov priljubljeni OS popolnoma varen. Absolutna varnost je seveda utopija, vendar se Linux drži treh konceptov, ki so sami po sebi zagotovilo za boljšo varnost: raznolikosti, preprostosti in odprtosti.

Pa poglejmo vsak koncept nekoliko podrobneje. Linux je raznolik, distribucij je silno veliko. Zlonamerna koda, ki izkorišča vrzel v aplikaciji, vključeni v določeno distribucijo, največkrat ni uporabna za kompromitiranje te iste aplikacije v drugih distribucijah. Vrzel seveda je, vendar je zlonamerna koda, brez specifičnih modifikacij, neuporabna. To nedvomno otežuje delo t. i. "skriptnim paglavcem", obenem pa učinkovito zmanjšuje/omejuje širjenje spletnih "črvov" in uporabo avtomatiziranih orodij. Vzemimo za primer prekoračitve medpomnilnika. V Oknih so znani t. i. univerzalni naslovi določenih operacij (npr. POP/POP/RET, JMP ESP, CALL ESP itd.) v nekaterih dinamičnih knjižnicah. Ti se nato sistematično uporabljajo pri izkoriščanju prekoračitev, saj delujejo v več različicah in servisnih popravkih sistema. Podobni "čudežni" naslovi so v Linuxu izključeni. V bistvu je v Linuxu popolnoma univerzalno le jedro. No, velja omeniti, da po novem ASLR (Address Space Layout Randomization) tudi v Oknih onemogoča uporabo univerzalnih naslovov.

Linux je preprost, saj se, če je le mogoče seveda, drži pozitivnega načela KISS (Keep It Simple Stupid :). Naslednja dva primera to nazorno ponazarjata: jedro 2.6 vsebuje približno 300 sistemskih klicev, Vista jih ima nekaj tisoč. Linuxov model nadzora dostopa pozna tri vrste dovolilnic (branje, pisanje, izvajanje - no, tudi posebneže SetUID/SetGID in sticky bit), ki določajo privilegije "uporabnikov", "skupin" in "drugih" na majhnem številu objektov (datoteke, mape, procesi). Okenski model pozna dve vrsti dovolilnic (dopusti, prepreči), ki določajo privilegije približno tridesetim skupinam, razdeljenim v dve podskupini: sistemsko (Local System, Local Service, Network Service) in uporabniško (Administrators, Everyone, Guest, Replicator, Authenticated Users, Power Users, Limited Users, Server Operators itd.). Skupine operirajo na večjemu številu objektov (datoteke, mape, procesi, storitve, cevi, registrski ključi, omrežni viri itd). Večja kompleksnost seveda pomeni večjo možnost napak. Problematiko dovolilnic in privilegijev obravnava tudi Microsoftovo varnostno opozorilo MS06-011. Mimogrede, okenski uporabniki, ki trdijo, da je Linux (pre)zapleten, tega sistema enostavno ne poznajo dovolj dobro. Po vsej verjetnosti ne poznajo dobro niti samih Oken. Pod "pokrovom" in globoko pod grafičnim vmesnikom so stvari veliko bolj zapletene.

Sicer pa je odprta koda v osnovi dobra stvar, vendar je predpostavka, "če več oči pregleduje kodo, bodo odkrite vse napake", lahko tudi dvorezen meč, ki daje lažen občutek varnosti. Mnoge pomanjkljivosti namreč odkrijejo neodvisni raziskovalci in ne skupnost razvijalcev. Torej strokovnjaki za varnost in ne programerji. Nemalokrat jih odkrijejo tudi t. i. "black hat" hekerji in ti jih največkrat zadržijo zase. Čeprav je seveda vedno možnost, da bo to isto napako nato odkril še nekdo drug, jo posredoval skupnosti in pobral vse zasluge. Linux je resda "Open Source", vendar je to za večino navadnih uporabnikov irelevantno. Kdo med nami sploh preverja in analizira kodo pred namestitvijo aplikacije? Znani so primeri vdorov v strežnike in poskusi vstavljanja stranskih vrat (backdoor) v izvirno kodo. Leta 2003 je bila odkrita zelo sofisticirana trojanska koda celo v izvirni kodi razvojne različice jedra (www.securityfocus.com/news/7388). Tarče podobnih napadov so bili tudi WordPress, SquirrelMail, BitchX in IRSSI. Po trditvah poznavalcev so taki poskusi sicer dokaj redki, pa tudi hitro odkriti in sanirani.

Transparentnost odprte kode je z varnostnega stališča nedvomno bolj sprejemljiva kakor praksa zaprte kode, vendar ščepec previdnosti tudi tu ne škoduje.

SetUID/SetGID

Besedila o varnosti v Linuxu seveda ne moremo začeti drugače kot z opisom dveh posebnih atributov - SUID (Set User ID) in SGID (Set Group ID). Atributa dovoljujeta uporabnikom, da ne glede na realni UID/GID (jedro identificira uporabnika po unikatni UID številki in ne po imenu) začasno prevzamejo efektivne SUID/SGID privilegije. Ti so sicer lahko v domeni poljubnega uporabnika ali skupine, nedvomno pa so z vidika varnosti najbolj sporne t. i. izvršne datoteke suid root, torej UID/GID 0, ki so v lasti korenskega uporabnika. Primer takega programa je npr. passwd, saj sprememba gesla povzroči modifikacijo datotek /etc/passwd in /etc/shadow, to pa je mogoče le s skrbniškimi privilegiji. Neprivilegiran uporabnik dobi tako možnost izvajanja privilegiranega procesa. Ravno zato so v preteklosti številni programi suid root podlegli prekoračitvam medpomnilnika, tekmovanju za vire in symlink napadom. Danes je stanje bistveno drugačno. Število privzetih programov suid root je okleščeno na minimum, razvijalci pa jim posvečajo še posebno pozornost. Vrzeli v programih SetUID 0 so danes silno redek pojav. Vse programe suid root v računalniku izpišemo z naslednjim ukazom:

find / -uid 0 -perm -4000 -print .

Uhajanje opisovalnikov datotek

Še ena oblika pomanjkljivosti, ki vse bolj odhaja na smetišče zgodovine, je uhajanje opisovalnikov datotek (file descriptor leakage). Opisovalnik je pozitivna številčna oznaka odprte datoteke znotraj procesa. Vsak proces v Linuxu pa ima vedno odprte tri standardne opisovalnike: standardni vhod (STDIN / 0), standardni izhod (STDOUT / 1) in standardni izhod za zapisovanje napak (STDERR / 2). Ko proces odpre nov I/O vir (datoteko, mapo, vtičnico, symlink ...), se novo ustvarjenemu opisovalniku poveča vrednost za +1. Podrejeni procesi (child process) od svojih staršev (parent process) podedujejo tudi odprte opisovalnike, to pa je lahko vir težav. Vzemimo za primer proces suid root, ki ima odprt opisovalnik do /etc/shadow. Program med izvajanjem ustvari nov proces, predhodno seveda "odvrže" root privilegije, podrejeni proces tako ne bo privilegiran, vendar ne zapre tudi opisovalnika do /etc/shadow. Tega podeduje podrejeni proces, ki bo tako, čeprav neprivilegiran, imel bralno-pisalni dostop do /etc/shadow. Konkreten primer uhajanja opisovalnikov najdemo tukaj:

labs.idefense.com/intelligence/vulnerabilities/display.php?id=208.

Symlink napadi

Simbolična povezava (symlink) je posebna oblika datoteke, ki rabi zgolj kot referenca do druge datoteke ali mape. Tako povezavo lahko ustvarimo z ukazom ln -s <datoteka> <ime_povezave>, njihova posebnost pa je, da jih lahko naredimo tudi na neobstoječih datotekah. In to brihtne buče sistematično izkoriščajo. Oglejmo si naslednji primer: imamo program SUID/SGID, ki v začasni imenik v določenih intervalih in na predvidljiv način zapisuje podatke. Napadalec mora torej "uganiti" ime datoteke in ji ustvariti simbolično povezavo, ki se bo sklicevala na, denimo, /etc/passwd. Program bo sledil povezavi ter zapisal podatke v datoteko /etc/passwd. Čeprav zveni kot znanstvena fantastika, je takih pomanjkljivosti v programski opremi kar precej. Znane so pod imenom "insecure tmpfile creation". Destruktivni način uporabe symlink napadov je torej precej trivialen. Nekoliko bolj zapletena je uporaba symlink datotek pri zapletenih napadih pridobivanja skrbniških privilegijev, saj mora napadalec dejansko vplivati na podatke, ki jih program suid root zapisuje v predvidljivo datoteko. Včasih pa se najdejo tudi izjeme: www.kde.org/info/security/advisory-20050905-1.txt. Ranljiva različica programa kcheckpass v mapo /var/lock zapiše datoteko kcheckpass.$UID, ki vsebuje niz naključno generiranih števil. Na niz sicer nimamo vpliva, vendar je dovolj kratek, da ga lahko uporabimo kot ime namenskega programa, ki se bo izvedel s privilegiji root. Brez symlinkov in iznajdljivosti seveda ne gre.

Pomanjkljivo preverjanje lastnosti datotek pred vnašanjem podatkov in slepo sledenje simboličnim povezavam sta primarni težavi programov, ki so dovzetni na napade symlink. Uporaba zastavice O_NOFOLLOW kot argument funkcije open() je pogosto primerna rešitev:

linux.die.net/man/2/open.

Tekmovanje za vire

Dodeljevanje časa in stanje izvajanja sta nadvse pomembna dejavnika vseh večnitnih (multi-threaded) in večopravilnih (multi-tasking) operacijskih sistemov. Tekmovanje za vire (race condition) je stanje, ko dva dogodka (procesa, niti, signala) poskušata doseči isti vir v istem trenutku. Zaradi neusklajenosti v tekmovanju "zmaga" naključni dogodek. Čeprav poznamo tudi ekstremno obliko tekmovanja, imenovano "smrtni objem" (deadlock), pri kateri v bistvu ne zmaga nihče, saj sta tekmovalca drug drugemu v napoto.

Tekmovanje za vire se najpogosteje kaže v dveh oblikah: "file race condition" (oz. TOCTOU) in "signal handling race condition". Signal je oblika medprocesne komunikacije (Inter-Proces Communucation). Lahko si jih predstavljamo kot programsko prekinitev (software interrupt), ki procesu dostavi nujno sporočilo. Ko proces prejme določen signal, nemudoma prekine delovanje ter izvede s signalom določeno operacijo. Race condition nastane, ko ima program za dva ali več različnih signalov določenega istega upravljavca (handler), to pa vodi do kolizije signalov in nepredvidljivega delovanja aplikacije. Zgled vrzeli: www.kde.org/info/security/advisory-20080426-2.txt. Mimogrede, Okna ne podpirajo upravljanja signalov.

Veliko bolj znana je nedvomno "file race condition" oblika. Jedro problema je kratek časovni interval med preverjanjem in izvajanjem, znanem kot TOCTOU (Time-Of-Check-Time-Of-Use). To je tudi eden od razlogov, zakaj Linux ignorira SUID/SGID bit v skriptah. Izvajanje skriptov je namreč dvostopenjski proces: jedro najprej preveri lastnosti skripte in nato zažene ustrezen tolmač (interpreter). Napadalec bi torej lahko izkoristil načelo TOCTOU in s symlinkom preusmeril izvajanje na drugo skripto.

Race condition vrzeli so v različnih oblikah sicer prisotne na vseh platformah in arhitekturah, vendar jih je najenostavneje izkoriščati prav na sistemih UNIX. Pogosto prav ob pomoči simboličnih povezav in skript, ki obremenijo delovanje sistema (npr. neskončna zanka) in s tem časovni interval nekoliko podaljšajo. Čeprav je izkušenim napadalcem tudi delček sekunde čisto dovolj.

Dereferenciranje kazalca NULL

Kazalec je spremenljivka, ki vsebuje pomnilniški naslov nekega podatka ali funkcije. Programerska praksa veleva, naj se kazalci ponastavijo z vrednostjo 0, s tem se namreč izogne napačnemu sklicevanju. O dereferenciranju kazalca NULL (NULL pointer dereference) govorimo, ko program želi dostop do podatkov na naslovu 0x0. Ker se naslovi nižjih lokacij v večini operacijskih sistemov privzeto ne preslikujejo (mapping), dereferenciranje nanje povzroči sesutje programa. Take pomanjkljivosti so veliko časa veljale za nepomembne, saj je prevladovalo prepričanje, da jih ni mogoče uspešno izrabiti, čeprav je bila že leta 1994 objavljena izkoriščevalska koda, ki je uporabljala kazalec NULL za pridobivanje skrbniških pravic na sistemu SCO UNIX.

Toda, kako je to sploh izvedljivo, če se naslov 0x0 dejansko ne preslikuje in je v vseh pogledih neveljaven? Preprosto, številni sodobni operacijski sistemi omogočajo tudi zaseganje (allocation) in preslikovanje nižjih pomnilniških lokacij - v UNIXu sorodnih platformah lahko, na primer, uporabimo sistemski klic mmap(). Tako dosežemo veljavnost naslova 0x0, posledično pa tudi vpliv na potek izvajanja. Če poenostavimo, NULL + odmik (offset) ne bo povzročil sesutja, saj bo program nemoteno nadaljeval izvajanje na podanem naslovu. Sklep: do nedavna "nepomemben" 0x0 kernel panic lahko nenadoma postane nadvse uporabna možnost za dvigovanje privilegijev. Celo prestižni OpenBSD, ki velja za najbolj zanesljiv in varen OS, ni povsem imun za tovrstne vrzeli (cve.mitre.org/cgi-bin/cvename.cgi?name=2007-0085).

Zadeva naj bi bila bistveno drugačna pri kazalcih NULL znotraj uporabniškega prostora, torej ob klasičnem sesutju programa. Morda pa tudi ne? Nedavno je IBMov strokovnjak Mark Dowd objavil dokument, v katerem je predstavil izkoriščanje kazalca NULL v predvajalniku Adobe Flash, prek katerega je pridobil sistemske privilegije v operacijskem sistemu Windows Vista SP1 in tako, najverjetneje, odprl povsem novo poglavje v metodologiji izkoriščanja omenjenih vrzeli (documents.iss.net/whitepapers/IBM_X-Force_WP_final.pdf).

Sistemski klic ptrace()

Notorični ptrace() sistemski klic omogoča, da nadrejeni proces (t. i. parent process) nadzira in manipulira izvajanje sledilnega procesa (t. i. tracing process), seveda le pod pogojem, da ima prvi (parent) ustrezne privilegije. Funkcionalnost je bila zamišljena kot pomoč programerjem pri razhroščevanju (debugging), vendar je ptrace() kmalu pritegnil tudi pozornost "netipičnih programerjev". Uporabo ptrace() tako zasledimo v številnih "exploitih" pa tudi naprednih metodah propagiranja zlonamerne kode, kot je npr. RPI (Runtime Process Infection), torej vrivanje kode v aktiven proces. V začetku leta je bilo odkritih več deset tisoč kompromitiranih linuxnih strežnikov, v katerih je bila z uporabo RPI v proces Apache vrinjena koda JavaScript, ta pa je nato izkoriščala vrzeli v okenskih sistemih. Vsekakor zelo napreden pristop, saj zlonamerna koda ni bila fizično prisotna v strežniku, temveč se je dinamično ustvarila ob obisku strani. Seveda z uporabo klica ptrace().

Praktičen prikaz je nedvomno lahko veliko bolj ekspliciten pokazatelj, zakaj je ptrace() potencialno nevaren. Tako vsaj meni avtor programa sudojump, s katerim lahko prestrežemo izvajanje programa sudo in mu podamo poljuben ukaz: www.quantumg.net/sudojump.php .

Sicer gre le za prikaz koncepta (proof of concept), vsekakor pa bodo pazljivi uporabniki opazili "nenavaden" UID v prvem terminalskem oknu. Možnosti zlorab nedvomno so.

Vejitvene bombe

Kako malo je treba, da spravimo na kolena kompleksne sisteme, lepo dokazujejo prav vejitvene bombe (fork bomb). Načelo delovanja je nadvse preprosto: proces se replicira po načelu rekurzivne vejitve, dokler ne zasede vseh sistemskih virov in popolnoma onemogoči delovanja sistema. Kako hitro doseže to točko, je sicer odvisno od zmogljivosti sistema, vendar le redki "zdržijo" več kot nekaj sekund. Celotno nalogo opravi sistemski klic fork(), ki omogoča ustvarjanje identičnih podrejenih (child) procesov. Mimogrede, ime "fork bomb" je verjetno nekoliko zavajajoče, saj Windows ne pozna funkcije fork(), temveč ekvivalent CreateProcess(). Različic rekurzivnih vejitev je resda silno veliko, saj lahko tudi programer začetnik precej hitro napiše svojo različico, vendar naslednji veljata za "umetniška primerka":

:(){ :|:& };: (Linux/BASH)

in

%0|%0 (Windows/CMD)

(ustrezna ukaza vpišite v datoteko, v okenskem primeru naj ima končnico BAT ali CMD in ju poženite)

Opozorilo: sistem bo postal neuporaben, potreben bo torej nov zagon.

Na srečo pa je tudi obramba pred rekurzivnimi bombami zelo preprosta - omejitev uporabe virov. V Linuxu lahko to storimo zelo preprosto. Najprej izvedemo ukaz ulimit -u ter preverimo privzete omejitve, nato pa v datoteko /etc/security/limits.conf vnesemo naslednji parameter: uporabnik hard nproc 500. Namesto "uporabnik" lahko vnesemo tudi simbol *, številčno vrednost pa lahko ustrezno prilagajamo, pač glede na potrebe in zmogljivost mlinčka. Kaj pa Windows? Verjeli ali ne, vendar Okna ne poznajo zaščite pred rekurzivnimi vejitvami.

Drži, tudi Pingvin ni brez napak. Vendar, kdor želi "informacijsko dozoreti", naj začne uporabljati Linux ter tako okusi duh časa, ko "so bili možje res možje". In občutek je res dober.

Omejitev uporabe virov uspešno prepreči vejitvene bombe (fork bomb).

Okna ne poznajo omejitve uporabe virov, zato jih lahko kratka skriptna vrstica %0|%0 spravi na kolena.

Program "sudojump" priča, da lahko sistemski klic ptrace() ponudi veliko možnosti tudi napadalcem.

"Kernel panic" je na Linuxu redek pojav, vendar lahko sesutje, ko gre za dereferenciranje kazalca NULL, izkoristimo za eskalacijo privilegijev ali izpisovanje pomnilnika Ring 0 (t. i. kernel memory dump).

Termin Linux je v članku namenjen operacijskemu sistemu GNU/Linux na splošno in ne določeni distribuciji. Različne oblike prekoračitev (buffer overflow) so namenoma izpuščene.

"Predpostavi, da je jedro ranljivo. Razen, če si trdno prepričan o nasprotnem."

anonimni jedrni heker

"Sesutij, ki so posledica dereferenciranja kazalca NULL, ni mogoče uspešno zlorabiti." - Yngve Pettersen, Opera Software.

Yngve se je zmotil.

Okenski uporabniki, ki trdijo, da je Linux (pre)zapleten, tega sistema enostavno ne poznajo dovolj dobro. Po vsej verjetnosti ne poznajo dobro niti samih Oken.

Naroči se na redna tedenska ali mesečna obvestila o novih prispevkih na naši spletni strani!

Komentirajo lahko le prijavljeni uporabniki

 
  • Polja označena z * je potrebno obvezno izpolniti
  • Pošlji