Tweets by @buherablog
profile for buherator at IT Security Stack Exchange, Q&A for IT security professionals

A BitBetyár Blog

Túljártál a nagyokosok eszén? Küldd be a mutatványodat! (e-mail a buherator gmailkomra jöhet)

Full-Disclosure / Névjegy / Coming out


Promó

H.A.C.K.

Címkék

0day (110) adobe (87) adobe reader (21) anonymous (26) apple (60) az olvasó ír (49) blackhat (20) botnet (22) bug (200) buherablog (44) buhera sörözés (39) bukta (49) deface (38) dns (22) dos (29) esemény (82) facebook (26) firefox (64) flash (33) gondolat (31) google (59) google chrome (36) hacktivity (37) hírek (117) incidens (224) internet explorer (88) iphone (35) java (50) jog (22) kína (21) kriptográfia (68) kultúra (21) linux (24) malware (43) microsoft (142) móka (48) mozilla (23) office (26) oracle (40) os x (43) patch (197) php (20) politika (31) privacy (58) programozás (22) safari (34) sql injection (62) windows (85) xss (77) Címkefelhő

Licensz

Creative Commons Licenc

Java Trusted Method Chaining - CVE-2013-1488

2013.05.28. 09:55 | buherator | 4 komment

James Forshaw, a Context Pwn2Own nyertes kutatója részletes leírást tett közzé a HP legutóbbi bugvadászatában kihasznált, és nem rég foltozott CVE-2013-1488 jelű hibával kapcsolatban. A probléma és a kihasználás módja amellett, hogy szépen bemutat néhány elterjedt Java exploit technikát, arra is példával szolgál, hogy milyen mély tudás, és speciális gondolkodásmód szükséges a menedzselt környezetek biztonsági hibáinak feltárásához.

Az alábbi leírás jórészt axt munkáját dícséri, aki vette a fáradságot, és a Contexis anyaga alapján reprodukálta az exploitot, és segített annak értelmezésében, ezúton is hatalmas köszönet neki ezért!

Bevezető

A CVE-2103-1488 egy ún. Trusted Method Chaining típusú sérülékenység, ami tipikusan a Java-hoz hasonló menedzselt, és bizonyos esetekben sandboxolt környezetek sajátja. Az ilyen környezetekben a "hagyományos" memóriakorrupciós sérülékenységek háttérbe szorultak (bár ahogy az axt blogján is látszik, nem tűntek el), mivel a törékeny memóriamanipulációs eljárások megvalósítására a keretrendszer szintjén, jól definiált, és jól ellenőrzött helyeken kerül sor. A futtatókörnyezetek tipikus felhasználási módja ugyanakkor általában eleve lehetővé teszi az általuk megvalósított virtuális gépek programozását, így a probléma egyel magasabb szintre tolódik: a felhasználó (programozó) számára eleve biztosított a kódfuttatás lehetősége, azonban a rendszer erőforrásainak elérését a biztonság érdekében korlátozni kell.

Ezeket a korlátozásokat egy komplex biztonsági architektúra definiálja (erről bővebben itt, itt és itt), ami nagy vonalakban az alábbi: Minden betöltött oszályhoz (helyesebben osztályok csoportjához, tipikusan java packagekhez) jogosultságok (Permission) halmazát rendeljük, melyeket a ProtectionDomain java osztály reprezentál. Ezek lehetnek statikus jogosultságok, vagy dinamikusan is eldönthetjük őket egy konfigurált Policy alkalmazásával, mint például az aláírt appletek esetében. A jogosultságok kiosztása az osztályok betöltésekor történik, amiért a ClassLoader a felelős. A jogosultságok ellenőrzését a SecurityManageren keresztül tehetjük meg, ami SecurityExceptiont generál, amennyiben az igényelt jogosultság nem áll rendelkezésre. Az aktuális SecurityManagert a System class egy statikus változója rögzíti, a sandbox escape tipikus célja ennek a változónak a null-ra állítása, és ezzel a sandbox korlátozásainak kikapcsolása.

Egy szál futtatása során tetszőleges pillanatban értelmezhető, az aktuális jogosultságok halmaza, amit AccessControlContextnek nevezünk. Ez tipikusan egy adott pillanatban a call-stacken szereplő osztályok ProtectionDomainjei által meghatározott jogosultságok metszete. Appletek esetében két domain-t különböztetünk meg: tetszőleges kód, ami a java runtime libraryból töltődik be "trusted", míg tetszőleges kód ami az appletből, "untrusted" kódnak minősül. Ezzel tudjuk korlátozni, hogy egy applet ne férjen hozzá a hálózathoz, a filerendszerhez, vagy sok más erőforráshoz vagy funkcióhoz. Amennyiben például megpróbálnánk a SecurityManagert null-ra állítani, a System osztály setSecurityManager hívása ellenőrzi, hogy rendelkezünk-e "setSecurityManager" permissionnel. Ez nem fog sikerülni, mert a hívó osztály (az általunk írt applet kód) ProtectionDomainje, viszonylag kevés jogosultsággal rendelkezik, és a jogosultságok metszete sem lehet bővebb mint az untrusted kód jogosultsága, és így SecurityException fog keletkezni.

Azonban az alapvető működéshez az untrusted kódnak is szüksége van bizonyos trusted funkcionalitás elvégzéséhez (pl. hozzáférés az applet beállításaihoz). Erre az AccessController doPrivileged metódusa ad lehetőséget, ami lehetővé teszi, hogy a trusted kód olyan műveleteket végezzen el az untrusted hívó részére, amihez annak önmagában nincs joga. Ilyenkor a trusted kód felelőssége, hogy korlátozza azt, hogy mit tesz elérhetővé. A doPrivileged hívása esetén nem a teljes stack alapján számítja a rendszer az aktuális jogosultságokat, hanem a doPrivileged-et hívó osztálynál megáll.

Egy egyszerű példa az AWT-ből. Bár az untrusted kódnak nem lenne joga a system property kiolvasására, mert nem rendelkezik "getProperty" permissionnel, a trusted osztálynak viszont szüksége van erre az információra a működéshez, még akkor is ha untrusted a hívó osztálya, ezért egy doPrivileged blokk segítségével olvassa ezt ki:

[...]
String graphicsEnv = AccessController.doPrivileged(new PrivilegedAction() {
	public String run() {
		return System.getProperty("java.awt.graphicsenv");	
	}
});
[...]

Fontos jól látni az következő példát, hogy a későbbieket könnyen megértsük. Adott egy A osztály a trusted domainből, valamint egy B osztály, ami az A leszármazottja viszont az untrusted domainben van. Amennyiben egy doPrivileged blokkból B egy xxx függvényét meghívnánk aminek valamilyen jogosultságra van szüksége, akkor SecurityExceptiont kapnánk, mert hiába csak a doPrivileged hívásig értékeljük ki a stack-et, a stacken a doPrivileged felett megjelent a B.xxx() metódushívás, így az effektív jogosutságok halmaza untrusteddé válik. Ez azonban nem igaz, ha az xxx függvényt A definiálja, és B-ben nem írjuk felül, ilyenkor ugyanis a stackre az A.xxx() hívás kerül, ami viszont trusted.

A Trusted Method Chaining nevű módszer lényege, hogy az öröklés lehetőségeit és a fenti példában látható tulajdonságot kihasználva úgy manipuláljuk az objektumaink állapotát, és olyan trusted chaineket hozzunk létre metódushívásokból, amelyek segítségével kontrollálni tudjuk a doPrivileged metódusban futtatott kódot.

A sérülékenység

A szóban forgó sérülékenység a megbízható kódként betöltődő java.sql.DriverManager osztály loadInitialDrivers() metódusában található, ez a metódus az osztály első inicializációjakor lefut:

private static void loadInitialDrivers() {
        String drivers; // jdbc.properties
        //...

        AccessController.doPrivileged(new PrivilegedAction() {
            public Void run() {

                ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator driversIterator = loadedDrivers.iterator();

        //...
                try{
                    while(driversIterator.hasNext()) {
                        println(" Loading done by the java.util.ServiceLoader :  "+driversIterator.next());
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
       //...
}

A bevezető alapján érezhető, hogy minket elsősorban a doPrivileged blokk fog érdekelni, ami látszólag nem sok mindent csinál, a háttérben azonban számos esemény lezajlik:

  1. Készítünk egy ServiceLoader objektumot, ami betölti az applet manifestjében meghatározott Driver osztályokat
  2. A betöltött osztályok Reflection-ön keresztül példányosodnak az iterátor hívásakor
  3. A példányosított objektumok toString() metódusa meghívódik a konzolra íráskor (!)

De hogyan lesz ebből cserebogár? Az rendben van, hogy készíthetünk saját Driver osztályokat, melyeket ez a kódrészlet betölt, de amint megpróbálunk valamilyen gonoszságot végrehajtani, a bizalmi lánc megszakad. Azoknak azonban, akik követik a rokon Java bugokat, a toString metódus nagyonis ismerős lehet: a Rhino Engine-nel kapcsolatos problémáról már a  volt szó a blogon, a hibát pedig ugyan már jó ideje javították, de Forshaw észrevette, hogy a módszer továbbra is használható lehet a fenti bizalmi lánccal kombinálva.

A Rhino Engine NativeError hibáját úgy javították, hogy a ScriptEngine konstruktora elmenti a hívó AccessControlContext-jét amikor az objektum létrejön, a JavaScript kód lefuttatása később ebben a kontextusban fog megtörténni. Ez azt jelenti, hogy ha sikerül egy ScriptEngine objektumot privilegizált kontextusban létrehozni, az objektumhoz rendelt JavaScript kód szintén privilegizáltan futhat majd le.

Ezt a következő módon lehet elérni:

  • Készítsünk egy FakeDriver1 osztályt, ami implementálja a Driver interfészt (így a ServiceLoader be fogja tölteni), és leszármazik a megbízható HashSet osztályból.
  • Definiáljuk felül a HashSet.iterator() metódust úgy, hogy a ServiceLoader<Object> iterátorát adja vissza
  • Az applet megfelelő konfigurációs fájljában adjuk meg a RhinoScriptEngine osztályt

Ilyen csillagállásnál a következő zajlik le a loadInitialDrivers() lefutásakor:

  1. A ServiceLoader<Driver> betölti a FakeDriver1 osztályt
  2. A driversIterator.next() példányosítja a FakeDriver1 osztályt
  3. A sztring konkatenáció miatt meghívódik a FakeDriver1 osztály HashSet-től örökölt toString() metódusa (megbízható kód)
  4. A toString() metódus elkéri a saját iterátorát, mire a FakeDriver1 felüldefiniált iterator() metódusa egy ServiceLoader<Object> iterátorral válaszol
  5. A toString() metódus végigpörgeti az iterátort, mire a ServiceLoader<Object> példányosítja és cache-eli a RhinoScriptEngine osztályt, még mindig privilegizált kontextusban - így a RhinoScriptEngine-nel betöltött JavaScript is privilegizáltan fog futni!

Ezek után nincs más dolgunk, mint készíteni még egy Driver implementációt (FakeDriver2), ami példányosodáskor elkéri FakeDriver1-től ServiceLoader<Object> iterátorát, és ezen keresztül a lecache-elt, privilegizált kontextusban futó RhinoScriptEngine objektumot használva lekapcsolja a Security Managert - vége a játkonak.

Utószó

Ha valaki elmorfondírozna azon, hogy vajon mi értelme van manapság Java exploitokat keresni, mikor az alapértelmezett biztonsági beállítások nem engedik az appletek alapértelmezett futását, azoknak ajánlott elolvasni az Immunity blogposztját a JRE7u13-ban javított sérülékenységről :

A csapat lénygében egy dokumentálatlan paraméterre bukkant a Java Web Start konfigurációjában, melyen keresztül globálisan megtiltható volt mindenfajta biztonsági ellenőrzés. Bár a támadási felület ezen a téren nyilván lényegesen kisebb, mint a trusted chainek, Reflection metódusok és egyéb csodák erdejében, azért nem lennék róla meggyőződve, hogy nem maradt még pár akna a Java nevű monstrumban.

Címkék: java tutorial az olvasó ír pwn2own james forshaw cve-2013-1488 trusted method chaining

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.