Blog

A bizalom tévedése: Hogyan szivárgott ki adat a több-bérlős Docker-beállításunkból (és hogyan javítottuk ki)

Ismerje meg, hogyan vezetett egy csapat naiv Docker-beállítása bérlők közötti adatszivárgáshoz, és a réteges izolációs stratégia, amely megakadályozta.

Összefoglaló

A Docker-konténerek alapértelmezés szerint nem izoláltak – megosztják a gazda kernelét, és szándékos konfiguráció nélkül a bérlők zavarhatják egymást. Ez a cikk egy valós forgatókönyvet mutat be, ahol egy több-bérlős tárhelyszolgáltató felfedezte, hogy az ügyfelek konténerei hozzáférhettek egymás adatbázisaihoz a megosztott hálózatkezelés és a gyenge biztonsági alapértelmezések miatt. Lépésről lépésre bemutatjuk a jogsértést kijavító változtatásokat: bérlőnként felhasználó által definiált hálózatok, nem root felhasználók, eltávolított képességek, csak olvasható fájlrendszerek és seccomp profilok. Gyakori feltételezés, hogy a konténerek erős izolációt biztosítanak; ezt megkérdőjelezzük azzal, hogy elmagyarázzuk, miért nyújtanak a VM-ek még mindig erősebb határt, és mikor érdemes hibrid megközelítést alkalmazni. A következtetés megerősíti, hogy az izoláció réteges gyakorlat, nem egyetlen jelölőnégyzet.

Az incidens: Amikor a konténerek túl sokat beszélnek

Beállítottál egy Docker-t egyetlen gazdagépen, hogy több ügyfél webhelyét futtassa. Minden ügyfélnek saját konténere van – szép, izolált környezet, igaz? Mi is ezt gondoltuk. Egészen addig, amíg egy rutin biztonsági audit ki nem derítette, hogy az A ügyfél konténere olvasta a B ügyfél konténerének MySQL socketjét ugyanazon a gazdagépen. Megosztották az alapértelmezett bridge hálózatot. Ráadásul a konténerek rootként futottak, így egy támadó, aki kompromittált egyet, hozzányúlhatott a gazda Docker socketjéhez vagy egy másik konténer fájlrendszeréhez. A jogsértés nem egy kifinomult exploit volt; alapvető hibás konfiguráció. Adatok szivárogtak. A bizalom eltűnt.

A hibás forgatókönyv nem ritka. Sok csapat feltételezi, hogy a Docker névterei és cgroupjai automatikusan elszigetelik a bérlőket, de alábecsülik, hogy mennyi szökési útvonal marad nyitva alapértelmezés szerint. Az alapértelmezett bridge hálózatok nem biztosítanak hálózati izolációt a konténerek között. Rootként futva a konténer több erőt kap, mint amennyire szüksége van. Explicit erőforráskorlátok nélkül pedig egy zajos szomszéd CPU-t vagy memóriát vonhat el másoktól.

1. lépés: Ne ossz meg egyetlen hálózatot

Az első javításunk az volt, hogy minden bérlőnek saját, felhasználó által definiált Docker hálózatot adjunk. Ez megakadályozza, hogy a konténerek elérjék egymást, hacsak nem kapcsolod össze őket explicit módon. Létrehoztunk egy szkriptet, amely minden bérlő számára létrehoz egy dedikált hálózatot, és csatolja hozzá az alkalmazáskonténerüket. Az adatbázis-konténer ugyanabban a bérlői hálózatban él, de hozzáadtunk egy belső hálózatot is, csak a bérlőn belüli kommunikációra. Nincs több bérlők közötti kukucskálás.

Az adatbázisokat is elkülönítettük: külön konténerekben futtattuk őket ugyanazon a bérlői hálózaton, külön adatkötetekkel. Ez biztosította, hogy még ha egy támadó be is jut az alkalmazáskonténerbe, ne szagolhassa ki egy másik bérlő adatbázisforgalmát.

A hálózati izolációs stratégiák részletesebb áttekintéséhez lásd a A gyakorlati Docker izolációs biztonsági ellenőrzőlista több-bérlős tárhelyhez című cikket.

2. lépés: Távolítsd el a szükségtelen jogosultságokat

A Docker-konténerek alapértelmezés szerint korlátozott Linux képességekkel futnak, de még így is több van, mint amennyire a legtöbb alkalmazásnak szüksége van. A konténereink rootként futottak, ami lehetővé tette a folyamatok számára olyan műveletek végrehajtását, mint a fájlrendszerek csatolása vagy kernel paraméterek módosítása. Áttértünk arra, hogy az alkalmazást nem root felhasználóként futtassuk a konténerben (a Dockerfile USER direktívájával), és eltávolítottuk az összes képességet, kivéve azokat, amelyek feltétlenül szükségesek. Egy tipikus webalkalmazás esetében ez lehet csak a NET_BIND_SERVICE (1024 alatti portok megkötéséhez) és a CHOWN (könyvtárakba íráshoz). Hozzáadtuk a --security-opt no-new-privileges opciót is a jogosultságkiterjesztés megakadályozására.

Ez a lépés önmagában kiküszöbölt számos gyakori konténerszökési vektort. Egy támadó, aki kompromittálja a webszervert, nem telepíthet csomagokat, nem módosíthat rendszer binárisokat, és nem férhet hozzá a gazda Docker socketjéhez, mert a folyamatból hiányzanak a CAP_SYS_ADMIN vagy CAP_DAC_OVERRIDE képességek.

3. lépés: Zárd le a fájlrendszert

Az írható fájlrendszerek gyakori támadási felületet jelentenek. Minden konténer esetében csak olvashatóvá tettük a gyökér fájlrendszert (--read-only), majd ideiglenes fájlrendszereket (tmpfs) csatoltunk azokhoz a könyvtárakhoz, amelyek írási hozzáférést igényelnek, például /tmp és az alkalmazás gyorsítótárkönyvtára. Ez megakadályozza, hogy egy támadó módosítsa az alkalmazás kódját vagy rosszindulatú binárisokat tároljon.

Ezenkívül a Docker --mount opcióját használtuk érzékeny könyvtárak, például a Docker socket kötött csatolására, csak akkor, ha feltétlenül szükséges – és soha éles konténerekben. Az elv: ha a konténernek nem kell írnia egy elérési útvonalra, tedd csak olvashatóvá.

4. lépés: Alkalmazz seccomp és AppArmor profilokat

Az alapértelmezett seccomp profilok már blokkolják a veszélyes rendszerhívásokat, de mi tovább testreszabtuk őket, hogy csak azokat a rendszerhívásokat engedélyezzük, amelyekre az alkalmazásunknak ténylegesen szüksége van. Ez kompromisszum, mert az alkalmazás profilozását igényli. Egyszerűbb megközelítés a Docker alapértelmezett seccomp profiljának használata, majd a --security-opt seccomp=path/to/profile.json hozzáadása, ha szigorúbb szabályokra van szükség. Hasonlóképpen, az AppArmor profilok korlátozhatják a konténer folyamatait bizonyos fájl elérési útvonalakra és képességekre. Engedélyeztük az AppArmort, és egy egyedi profilt használtunk, amely csak az alkalmazás adatkönyvtáraihoz korlátozta a hozzáférést.

A megerősítési lépések átfogó útmutatójáért lásd: Docker konténerek megerősítése több-bérlős tárhelyhez: Lépésről lépésre izolációs útmutató.

Az ellentétes nézet: Néha VM-ekre van szükség

Bármennyire is megerősítettek, a konténerek megosztják a gazda kernelét. Egy kernel-sérülékenység egyszerre törheti meg az összes izolációt. Ezért futtatnak sok biztonságtudatos platform konténereket könnyű VM-ek belsejében – minden bérlő saját kernelt kap. Ez többletterhelést jelent, de hardverszintű határt biztosít, amit a konténerek önmagukban nem tudnak nyújtani. Ha bérlői hitelkártyaadatokat vagy egészségügyi nyilvántartásokat kezelnek, a hibrid megközelítés (konténerek VM-ekben) lehet a helyes választás. Ne feltételezze, hogy a konténerizoláció elegendő a fenyegetési modelljéhez; értékelje az adatok érzékenységét és a szabályozási követelményeket.

Az izolációs szintek részletesebb összehasonlításához olvassa el a Több-bérlős Docker architektúra tervezése: A megfelelő izolációs szint kiválasztása című cikket.

Következtetés: Az izoláció rétegekből áll, nem egy kapcsolóból

A javítás nem egyetlen változtatás volt – rétegezés: hálózati izoláció, korlátozott jogosultságok, csak olvasható fájlrendszerek és rendszerhívás-szűrés. Még így is elfogadtuk, hogy a tökéletes izoláció lehetetlen a megosztott kernellel rendelkező konténereknél. A legmagasabb biztonsági igényű bérlőink számára dedikált gazdagépre helyeztük őket. A tanulság: ne bízz semmilyen alapértelmezésben. Ellenőrizze a Docker beállításait úgy, mintha a jogsértés már megtörtént volna. A lezárás ideje a szivárgás előtt van, nem utána.

Sources (5)