A rename() biztonsága: miért kevésbé veszélyes, mint gondoltuk
A rename() biztonsága: miért kevésbé veszélyes, mint gondoltuk
Miközben a fájlrendszeres kutatást végeztem, találtam egy különösen rossz példát, amelyet előadásaimban és diáimban mutattam be a rename() működéséről, és úgy éreztem, ésszerű lenne vállalni ezt a hibát és közzétenni a javítást.
A bemutatott példám a rename("./a", "./b") nem racy. Ez a rename() egy speciális eset, amely valójában (valamilyen módon) biztonságos; további részletek lentebb találhatók.
Abszolút útvonalú rename() műveletek nem érintettek: (például rename("/Users/test/tmp/a", "/Users/test/tmp/b")).
Relatív útvonalú átnevezések nem érintettek, ha a CWD-ben van egy támadó által ellenőrzött alkönyvtár (például rename("./tmp/a", "./tmp/b")). Ebben az esetben, ha a tmp alkönyvtár támadó által ellenőrzött, a race továbbra is fennáll.
Fontos megjegyezni, hogy a . nem vezet valós útvonalfeloldáshoz, legalábbis a legfrissebb tesztjeimben. Ennek is vannak korlátozásai, további részletek lentebb.
Összességében sajnálatos hibát követtem el, hogy ezt a speciális esettípust használtam, amely – rendkívül bosszantó módon – ritka, és valós életben nem kihasználható. Minden példának a helyes rename("./tmp/a", "./tmp/b") formátumot kellene tartalmaznia.
Ha ez bármilyen módon hatott rád, elnézést kérek. Egy sörre vagyok adó.
A példát, amelyet eredetileg a diámban és előadásomban szerettem volna tenni, a lateralus exploitból származtattam, ahol a rename() race condition-ját tapasztaltam. Ebben az exploitban két hosszú abszolút útvonalú rename() művelet volt. Megkiselepítettem ezeket az útvonalakat, mert a (hibás) feltevésem szerint nem számít.
Abszolút útvonalak esetén bármely nem utolsó útvonalkomponensre race condition-t lehet létrehozni, míg relatív útvonalak esetén a CWD alatt bármely nem végső útvonalkomponensre kell race condition-t létrehozni. Alapvetően, ha a relatív rename() bármely alkönyvtárban történik, race condition van; ha nincs (támadó által ellenőrzött) alkönyvtár, nincs. További bonyolítást jelent, hogy a speciális . könyvtár nem számít alkönyvtárnak.
Ahogy elképzelhető, ilyen dolog tesztelése igazán nehéz helyesen elvégezni. Úgy vélem, hibát követtem el a tesztelő kódban. Nem vizsgáltam meg annyira alaposan, mint kellett volna, mivel a valós életbeli exploitjaim működtek, ami érthető, mert egyikük sem használt ki ezt a konkrét esetet.
Ez az eset gyakori, de csak egy nagyon kis részhalmaz exploitálható. Általában ebben az esetben a két fájlnév (forrás és cél) nem támadó által ellenőrzött. Az itt felmerülő kihasználási probléma az, hogy mivel nem tudunk felülírni egy tetszőleges fájlnevet, a végső cél egy „felülírás/hozzárendelés egy fix, statikus fájlnévvel bármely könyvtárban” primitív.
Ez a kihasználás bizonyos helyzetekben lehetséges. Egy ilyen helyzet az, ha a sérülékeny alkalmazás root-ként fut. Ebben az esetben egy véletlenszerű fájlnevet helyezhetünk root-ként egy tetszőleges könyvtárba, és célozhatjuk a sudo-t.
A /etc/sudoers.d célzása root LPE-t eredményez, mivel a sudo nem foglalkozik a fájl nevével. A jogosultságok azonban fontosak, de a race miatt teljesen irányíthatjuk a forrás inode-ot, és használhatunk némi okos trükköket a ellenőrzés megkerülésére, vagy egyszerűen szimbolikus linket használhatunk. Ismét, kicsit leegyszerűsítve, de ez nem túl nehéz. Ilyen hibákat is benyújtottam.
Egy másik helyzet, ahol ez kihasználható lehet, ha a támadó ellenőrizte a fájlnevet. Mivel most a támadó visszaválthatja a speciális esetet a nem speciális esetre – egy alkönyvtár bevezetésével a fájlnevebe – a kihasználás triviális. Azonban az összes, amivel találkoztam, szerint ez rendkívül ritka.
Végül, ha a támadó valójában nem ellenőrizte a fájlnevet, és a sérülékeny alkalmazás nem fut root-ként, nincs jó kihasználási vektor, amit említenék. Próbáltam általános megoldást találni, hogy ezeket kihasználható helyzetekké alakítsam, de a legtöbb jogosult alkalmazás egyszerűen fix nevekű fájlokat keres, és a hozzáférési könyvtárakban véletlenszerű fájlok létrehozása hatástalan.
Ez azonban a következő határvonal: Ha talál egy privilegizált alkalmazást, amelyet támadhatsz egy statikus véletlenszerű névvel rendelkező fájl elhelyezésével valahol, vedd fel velem a kapcsolatot. Egy ilyen támadási vektor lenne a kulcs sok (jelenleg) nem kihasználható hibát feloldásához. Még ha a rename() sem olyan race condition, mint eredetileg gondoltam.
Összefoglalva, a rename() valójában kevésbé ijesztő, mint amit eredetileg javasoltam, azonban a gyakorlatban ez a hatás meglehetősen kicsi.
Ez a művelet biztonságos, mert: a program CWD-je nem módosítható, és a . nem vezet újra feloldáshoz a jelenlegi könyvtárra. Ha valamelyik feltétel megsérül, problémát okozhat.
Először is gyanítom, hogy a . feloldása vagy egy cache, vagy egy speciális eset a kernel valahol, azonban nem garantálhatom, hogy ez minden helyzetben, fájlrendszerben, speciális esetben igaz. Mivel a kernel útvonalfeloldása (különösen a namei() és lookup()) elképesztően bonyolult, túl mélyen vizsgálni ezt nem olyan, amihez most időt szánhatok. Mindez bonyolultsága ellenére meglepő lenne, ha ez a feltétel minden esetben érvényes lenne.
Másodszor, a CWD módosíthatatlansága kapcsán: ez nem igaz macOS-on. Linuxon ez úgy működik, ahogy várható (a program még mindig látja a régi könyvtárat), de macOS-on egyszerűen felmountolhatod a CWD könyvtárát (vagy bármely előző útvonalkomponensét), és a kernel csendben felcseréli a könyvtár vnode-ját a futó alkalmazás alatt.
Ez valóban riasztó.
Ha ezután használod az umount -f parancsot a mount-ön, örökre leállíthatod az alkalmazást, minden relatív útvonalú fájlműveletet hibára ítélve, amíg a célzott alkalmazás újra inicializálja a CWD-jét abszolút úttal. Viccesen, de ../ használata nem fog működni. Nem kell mondanom, hogy ez mindegyik rendkívül furcsa.
Bármilyen esetben is, a trükk hasznossága vitatható, és a fejemből nem tudok jó trükköket, amelyek hasznos hatást érnek el, de ez a viselkedése az operációs rendszer jellemzője. Kísérleteztem ezzel, de a legjobb, amit elérhettem, az volt, hogy a rename() műveletet átirányítottam a mount pontra. Mivel azonban nem tudtam külsőleg visszaállítani a régi CWD-t a célprogramban, ez haszontalanná vált.
Ugyanakkor ugyanaz vonatkozik, mint korábban: Ez sok további kutatást igényel, így küldj DM-et, ha van jobb tipp.
Ezek hibáinak javításaként frissítettem az összes releváns blogbejegyzést és diát, hogy tartalmazzák a pontos információkat, és ennek megfelelően jelölöm a változtatásokat.
Feltöltöttem a blogra a javított diákat az AlligatorCon és OBTS számára. Emellett javítottam a lateralus blogbejegyzést.