Defenzív programozás
A defenzív programozás a defenzív tervezésnek egy olyan formája, amelynek az a szándéka, hogy biztosítsa a szoftver bizonyos részeinek folyamatos működését előreláthatatlan körülmények (például helytelen bevitelek) között. Gyakran használják, amikor szükség van magas szintű elérhetőségre és megbízhatóságra.
A defenzív programozás a szoftvert és a forráskódot hivatott javítani a következő szempontok alapján:
- Általános minőség – csökkenteni a szoftverhibákat és problémákat
- A forráskód érthetővé tétele – a forráskódnak olvashatónak és érthetőnek kell lennie, jóváhagyhatónak kódellenőrzéssel
- A szoftver viselkedését kiszámíthatóvá kell tenni váratlan bevitel és felhasználói cselekedetek esetén is
A túlzásba vitt defenzív programozás (mely olyan problémákat is próbál kezelni, amelyek soha nem merülhetnek fel) növeli a futási időt és a karbantartási költségeket; továbbá túl sok kivételt kezel, így potenciálisan észrevétlen, helytelen eredményeket adhat.
Biztonságos programozás
[szerkesztés]A biztonságos programozás a defenzív programozás egyik alfaja, amiben a számítógép biztonsága érintett. Itt a biztonság az elsődleges, nem a megbízhatóság vagy elérhetőség (lehet, hogy a szoftver bizonyos módon meghibásodhat). Mint mindenfajta defenzív programozásnak, ennek is szoftverhibák elkerülése az elsődleges célja; azonban a motiváció nem az, hogy csökkentse a meghibásodás valószínűségét normál működés esetén, hanem hogy csökkentse a támadási felületet. A programozónak tisztában kell lennie azzal, hogy a szoftvert nem a rendelkezésnek megfelelően használják, és megpróbálják rosszindulatúan kihasználni a szoftverhibákat.
int risky_programming(char *input) {
char str[1000];
// ...
strcpy(str, input); // Bevitel másolása.
// ...
}
A függvény meghatározatlan viselkedést eredményez, ha a bevitel meghaladja az 1000 karaktert. Egyes kezdő programozók ezt nem tekintik problémának, mert feltételezik, hogy egyetlen felhasználó sem ír be ilyen hosszú bemenetet; a valóságban azonban lehetővé teszi a puffertúlcsordulás kihasználását. A megoldás erre az esetre:
int secure_programming(char *input) {
char str[1000+1]; // Még egy karakter a null számára.
// ...
// Bevitel másolása a cél hosszának túllépése nélkül
strncpy(str, input, sizeof(str));
// Ha strlen(input) >= sizeof(str), akkor az strncpy nem végződik null-lal.
// Ekkor a puffer utolsó karakterét mindig null-ra állítjuk,
// elvágva a bevitelt az általunk kezelhető maximális hosszúságnál.
// Dönthetünk úgy is, hogy megszakítjuk a programot, ha a bevitel túl hosszú.
str[sizeof(str) - 1] = '\0';
// ...
}
Offenzív programozás
[szerkesztés]Az offenzív (támadó) programozás a defenzív programozásnak egy olyan kategóriája, amely arra összpontosít, hogy bizonyos hibákat nem kell defenzíven kezelni. Itt csak a kívülről származó hibákat (mint például a felhasználói bevitelt) kezelik, és megbíznak a szoftver, valamint a program védelmi vonalán belüli adatok helyességében.
Megbízás a belső adatok helyességében
[szerkesztés]Túlságosan defenzív programozás
[szerkesztés]const char* trafficlight_colorname(enum traffic_light_color c) {
switch (c) {
case TRAFFICLIGHT_RED: return "red";
case TRAFFICLIGHT_YELLOW: return "yellow";
case TRAFFICLIGHT_GREEN: return "green";
}
return "black"; // Nem működő lámpaként kell kezelni.
// Figyelem: Az utolsó 'return' utasítás egy optimalizáló fordító elveti, ha a
// 'traffic_light_color' összes lehetséges értéke szerepel a 'switch' utasításban
}
Offenzív programozás
[szerkesztés]const char* trafficlight_colorname(enum traffic_light_color c) {
switch (c) {
case TRAFFICLIGHT_RED: return "red";
case TRAFFICLIGHT_YELLOW: return "yellow";
case TRAFFICLIGHT_GREEN: return "green";
}
assert(0); // Ellenőrzés, hogy ez a szakasz elérhetetlen.
// Figyelem: Az 'assert' függvényt egy optimalizáló fordító elveti, ha a
// 'traffic_light_color' összes lehetséges értéke szerepel a 'switch' utasításban
}
Megbízás a szoftver-elemekben
[szerkesztés]Túlságosan defenzív programozás
[szerkesztés]if (is_legacy_compatible(user_config)) {
// Stratégia: Ne bízzunk abban, hogy az új kód ugyanúgy viselkedik
old_code(user_config);
} else {
// Alternatíva: Ne bízzunk abban, hogy az új kód kezeli ugyanazokat az eseteket
if (new_code(user_config) != OK) {
old_code(user_config);
}
}
Offenzív programozás
[szerkesztés]// Számítsunk arra, hogy az új kódnak nincsenek új hibái
if (new_code(user_config) != OK) {
// Tudassuk, hogy gond van, és lépjünk ki
report_error("Ez nem jött össze");
exit(-1);
}
Technikák
[szerkesztés]Néhány defenzív programozási technika:
Forráskód intelligens újrafelhasználása
[szerkesztés]Ha van egy tesztelt és működő kód, annak újrahasználata lecsökkenti az új hibák felbukkanásának esélyét. Ennek ellenére a kód-újrafelhasználás nem mindig jó gyakorlat, mivel felerősíti az eredeti kódra mért esetleges támadás következményeit. Ebben az esetben az újrafelhasználás komoly üzleti folyamathibákat okozhat.
Örökölt problémák
[szerkesztés]Mielőtt újrafelhasználásra kerül a régi forráskód, könyvtárak, API-k, konfigurációk és így tovább, meg kell győződni arról, hogy van-e értelme azokhoz ragaszkodni, és nem fognak-e inkább probléma-öröklődéshez vezetni. Ha a régi megoldásoktól azt várják el, hogy a mai követelményeknek megfelelően működjenek (különösen akkor, amikor azokat nem fejlesztették vagy tesztelték az új igényeknek megfelelően), akkor valószínűleg örökölt problém1<k fognak megjelenni.
Sok szoftverterméknek problémája akadt a régi, örökölt forráskóddal:
- Lehetséges, hogy a régi kódot nem a defenzív programozás alapján tervezték, így sokkal alacsonyabb minőségű lehet, mint egy újonnan írt forráskód.
- Lehetséges, hogy a régi kódot olyan körülményekhez írták és tesztelték, amelyek ma már nem alkalmazandóak. A régi minőségbiztosítási tesztek már nem érvényesek. Példák:
- A régi kódot ASCII bevitelre tervezték, de a mostani bevitel UTF-8.
- A régi kódot 32 bites architektúrákon kompilálták és tesztelték, de 64 bites architektúrákra fordítva aritmetikai problémák merülhetnek fel (előjelek, típuskényszerítés stb).
- A régi kódot offline gépekre írták, és a hálózati kapcsolat hozzáadásával sebezhetővé válik.
- A régi kód nem tartja szem előtt az új problémákat. Például egy 1990-ben írt forráskód valószínűleg sok kódinjekciós sebezhetőségre hajlamos, mivel a legtöbb ilyen problémát akkor még nem ismerték széles körben.
Ismert példák az örökölt problémákra:
- BIND, amelynek problémáit Paul Vixie és David Conrad ismertette,[1] a biztonságot, robosztusságot, skálázhatóságot és az új protokolokat megnevezve, mint a fő okokat, hogy újraírják a régi kódot.
- A Microsoft Windows „metafájl sebezhetősége” és a WMF formátumhoz kapcsolódó egyéb biztonsági rések. A Microsoft Security Response Center szerint mikor 1990 körül bevezették a WMF-támogatást, még nem a Microsoft biztonsági kezdeményezései alapján dolgoztak.[2]
- Az Oracle is küzd az örökölt problémákkal, például a régi forráskóddal, amely nem kezelte a SQL-injekciót és a privilégium-eszkalációt, és ez számos biztonsági rést eredményezett, amelyek elhárítása időt vett igénybe, és egyesekre hiányos javításokat készítettek. Ez komoly kritikákat váltott ki olyan biztonsági szakértők részéről, mint David Litchfield, Alexander Kornbrust, Cesar Cerrudo.[3][4][5] További kritika, hogy az alapértelmezett telepítések (nagyrészt a régi verziók örökségei) nincsenek összhangban az Oracle saját biztonsági ajánlásaival, például az Oracle Database Security Checklist-tel, amelyet nehéz módosítani, mivel sok alkalmazás megköveteli a örökölt (és kevésbé biztonságos) beállításokat a megfelelő működéshez.
Kanonizálás
[szerkesztés]A rosszindulatú felhasználók valószínűleg újfajta adatábrázolásokat találnak ki helytelen bevitelhez. Például, ha egy program megpróbálja elutasítani az "/etc/passwd" fájlhoz való hozzáférést, akkor a cracker más formában adhatja meg a fájlnevet, például "/etc/./passwd". A kanonizációs könyvtárak alkalmazhatók a nemkanonikus bemenet által okozott hibák elkerülésére.
Alacsony tolerancia "potenciális" hibák ellen
[szerkesztés]Tegyük fel, hogy a problémára hajlamos kódkonstrukciók (hasonlóan az ismert sebezhetőségekhez stb) programhibák és potenciális biztonsági hibák. Az alapvető ökölszabály: „Nem ismerem az exploitok minden típusát. Védekeznem kell azok ellen, amelyeket ismerem, utána pedig proaktívnak kell lennem!”
Egyéb technikák
[szerkesztés]- Az egyik legáltalánosabb probléma a konstans méretű struktúrák és függvények ellenőrizetlen használata dinamikus méretű adatokhoz (a puffertúlcsordulás problémája). Ez különösen általános a szöveges bevitelnél C-ben. Olyan C függvényeket, mint például a gets, soha nem szabad használni, mivel a bemeneti puffer maximális mérete nem szerepel paraméterként. Azonban a scanf biztonságosan használható, bár követelmény, hogy a programozó használat előtt ellenőrizze a bevitelt.
- A hálózatokon keresztül továbbított összes fontos adat titkosítása / hitelesítése. Ne próbáljunk meg saját titkosítási sémát megvalósítani, használjunk bevált sémákat.
- Minden adat fontosnak tekintendő, amíg ellenkezője be nem bizonyosodik.
- Minden adat hibásnak tekintendő, amíg ellenkezője be nem bizonyosodik.
- Minden kód károsnak tekintendő, amíg ellenkezője be nem bizonyosodik.
- Semmilyen kódnak a biztonságát nem lehet bebizonyítani, ami a felhasználóktól származik, vagyis „soha ne bízz az ügyfélben”.
- Ha egy adat helyességét kell ellenőrizni, akkor azt kell vizsgálni, hogy helyes-e, nem pedig azt, hogy helytelen-e.
- Szerződésalapú programozás
- A szerződésalapú programozás előfeltételeket, utófeltételeket és invariánsokat használ annak biztosítására, hogy a megadott adatok (és a program egészének állapota) tiszta. Ez lehetővé teszi a kód számára, hogy dokumentálja feltételezéseit, és biztonságos módon tegye ezt. Például a függvény vagy metódus argumentumai érvényességének ellenőrzése a függvény törzsének végrehajtása előtt. A függvény törzse után az állapot vagy más tárolt adatok, valamint a visszaadott érték ellenőrzése is javallott.
- Állítások (más néven asszertív programozás)
- A függvényeken belül kívánatos ellenőrizni (assert), hogy nem egy érvénytelen (null) adatot kívánunk referenciálni, és hogy a tömb hossza érvényes-e az elemek hivatkozása előtt, különösen az ideiglenes / helyi példányok esetében. Jó módszer az is, ha nem bízunk azokban a könyvtárakban, amelyeket nem magunk írtunk, és mindig ellenőrizzük az onnan kapott adatokat. Ezekhez gyakran ajánlott létrehozni egy kis "érvényesítő" és "ellenőrző" naplózó függvénykönyvtárat, hogy nyomon kövessük az utat és csökkentsük a debug szükségességét. A naplózási könyvtárak és a aspektusorientált programozás megjelenésével a védekező programozás körülményes aspektusai enyhültek.
- Kivételek visszaadása kódok helyett
- Általánosságban elmondható, hogy előnyösebb olyan érthető kivételüzeneteket dobni, amelyek dokumentálva vannak az API-ban egyszerű kódok visszaadása helyett.
Jegyzetek
[szerkesztés]- ↑ Paul Vixie and David Conrad on BINDv9 and Internet Security. impressive.net . (Hozzáférés: 2018. október 27.)
- ↑ „Looking at the WMF issue, how did it get there?”, MSRC. [2006. március 24-i dátummal az eredetiből archiválva] (Hozzáférés: 2021. május 1.) (amerikai angol nyelvű)
- ↑ Litchfield, David: Bugtraq: Oracle, where are the patches???. seclists.org . (Hozzáférés: 2018. október 27.)
- ↑ Alexander, Kornbrust: Bugtraq: RE: Oracle, where are the patches???. seclists.org . (Hozzáférés: 2018. október 27.)
- ↑ Cerrudo, Cesar: Bugtraq: Re: [Full-disclosure RE: Oracle, where are the patches???]. seclists.org . (Hozzáférés: 2018. október 27.)
Források
[szerkesztés]- Defenzív programozás. inf.nyme.hu. (Hozzáférés: 2021. június 4.)[halott link]
- Defensive Coding Guide. Red Hat Developer. (Hozzáférés: 2021. június 4.)
- The art of defensive programming. Sudonull. (Hozzáférés: 2021. június 4.)
- Defensive Programming - Friend or Foe?. Interrupt. (Hozzáférés: 2021. június 4.)
- Introduction to Software Systems – Lecture 18. cs.mcgill.ca. (Hozzáférés: 2021. június 4.)
További információk
[szerkesztés]Fordítás
[szerkesztés]Ez a szócikk részben vagy egészben a Defensive programming című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.