Amit tudnod kell fejlesztőként, V. rész: Biztonság
A szoftverrendszerek biztonsági kérdései önmagukban óriási témakört alkotnak, nem véletlen, hogy az informatikának ez az ága tulajdonképpen külön szakmának is tekinthető. Vannak azonban olyan alapvető fogalmak és koncepciók, amiket minden fejlesztőnek ismernie kell, nem csak azoknak, akik kifejezetten securityvel foglalkoznak — ebben a posztban néhány ilyet ismertetünk.
Ne bízz semmilyen inputban!
Bármilyen szoftver elsőszámú támadási felülete a felhasználói bemenet (webes form, parancssori paraméter, stb.), így ezt kiemelten kell kezelnünk. Fontos tehát, hogy bármilyen, alapvetően nem megbízható forrásból érkező inputot validálni kell (megbízható forrásnak ebben a kontextusban tulajdonképpen csak a saját alkalmazásunk egy komponense tekinthető).
Ebben a témakörben a leggyakoribb hiba, ha az inputban megpróbálunk "illegális" értéke(ke)t keresni. Ez tulajdonképpen afféle fekete lista stratégia: csinálunk egy listát az általunk nem elfogadhatónak vélt inputokról (input típusokról), és ha a bejövő érték rajta van ezen a listán, nem fogadjuk el. A nyilvánvaló probléma itt az, hogy a támadónk általában okos, és mindig ki tud találni egy újabb olyan illegális értéket, ami nincs rajta a listánkon.
Ennél sokkal jobb, ha inkább egy whitelist jellegű megoldást alkalmazunk; meghatározzuk a megengedett input értékek körét, és ha a bejövő érték ennek nem felel meg, nem engedjük át. Ez természetesen magában hordozza azt a veszélyt, hogy a szabályrendszerünk túl szigorú lesz, és nem engedünk be érvényes értékeket, de ezt egyrészt a felhasználóink valószínűleg gyorsan jelezni fogják, másrészt pedig még mindig sokkal jobb, mintha átengednénk egy illegális értéket.
Nézzük meg ezeket az elveket egy egyszerű példán: az alkalmazásunkban fájlneveket kell validálnunk. Válasszuk először a blacklist stratégiát! Tudjuk például, hogy a "/" karaktert valószínűleg hiba lenne megengedni a fájlnévben, így ezt felvesszük a fekete listánkra. Érezhető persze, hogy ezt az egyetlen karaktert vizsgálni valószínűleg kevés lesz. Mi van például a tabulátor, soremelés, és egyéb vezérlő karakterekkel? Lehet, hogy a szóközök is problémásak. Nehéz lenne összeszedni minden esetet még ebben az egyszerű esetben is, és egy hacker valószínűleg úgyis találna olyan kiskaput, amire nem gondoltunk. Ennél sokkal egyszerűbb, ha megvizsgáljuk inkább, hogy a fájlnév megfelel-e egy mintának, amiről tudjuk, hogy legális, és mindent megtiltunk, amit ez a minta nem fed.
Erről bővebben olvashattok az IBM DeveloperWorks posztjában, ahol különböző gyakran előforduló adattípusokra (számok, stringek) sok best practice jellegű tanácsot is megemlítenek.
Biztonságos hibakezelés
Ez egy következő fontos alapkoncepció, az angol szakirodalomban legtöbbször a "fail securely" névre hallgat, és a programunk hibakezelésének security oldalát boncolgatja. Foglalkozzunk azzal az esettel, amikor magában a biztonsági komponensünkben történik valamilyen kivétel. Egy biztonsági rutinnak alapvetően három kimenetele lehet: művelet engedélyezése, művelet tiltása, vagy kivétel (hiba). Nagyon fontos, hogy ha kivétel történik, nem állhat elő olyan állapot, amit a művelet tiltása nem engedett volna meg. Vegyünk például egy (leegyszerűsített) kódrészletet, ami eldönti a felhasználóról, hogy rendelkezik-e adminisztrátori joggal:
isAdmin = true;
try {
blabla(); // Ez a kód kivételt generálhat
isAdmin = userHasRole("admin");
} catch(Exception e) {
// Kivétel kezelése
}
Remélhetőleg elsőre szemet szúr a hiba ebben a "programban": ha a blabla()
metódushívás valamilyen kivételt generál, a felhasználót a valós vizsgálat lefutása nélkül adminisztrátornak minősíthetjük! A megoldás a logika megfordítása, amely ebben az esetben meglehetősen egyszerű:
isAdmin = false;
try {
blabla(); // Ez a kód kivételt generálhat
isAdmin = userHasRole('admin');
} catch(Exception e) {
// Kivétel kezelése
}
Természetesen a való életben ennél sokkal bonyolultabb formákban tud jelentkezni ez a probléma, de a lényeg ugyanaz.
Támadási felület minimalizálása, "least privilege", rekeszesítés
Ezek a fogalmak némileg összefüggenek, legalábbis az alapvető ötletet tekintve. A támadási felület minimalizálása egyszerűen azt jelenti, hogy a szoftverünkben a kódbázis lehető legkevesebb részét tesszük ki nem megbízható külső felhasználóknak (a "felhasználó" persze itt nem feltétlenül emberi lény).
A "least privilege" elve alatt azt értjük, hogy az authorizációs stratégiánk egy felhasználónak csak az általa elvégzendő feladathoz minimálisan szükséges jogosultságokat biztosítja, a lehető legkevesebb időre.
A rekeszesítés során a szoftver által tárolt adatokat egymástól elkülönített részekre (rekeszekre) osztjuk, és a különböző rekeszekhez csak azoknak biztosítunk hozzáférést, akiknek arra feltétlenül szükségük van. Ez azért fontos, mert ha valamilyen jogosulatlan hozzáférés történik, még mindig nem feltétlenül tettük ki a teljes alkalmazást/adathalmazt veszélynek.
Ez a pár fogalom persze mégcsak a jéghegy csúcsának sem tekinthető, számos könyv és cikk foglalkozik azzal, hogyan írjunk biztonságos kódot. Ezek közül az egyik legjobb (amit valószínűleg mindenkinek végig kellene olvasnia egyszer ) a Writing Secure Code 2 — ha még nincs meg, érdemes beszerezni és nekiveselkedni.