A HWSW egész jó összefoglalót közölt a Reginaldo Silva által felfedezett, a Facebook által 33.500 dollárral jutalmazott sérülékenységről. Most a Sensepost közölt egy mélyebb technikai összefoglalót, melyből kiderül néhány fontos részlet azzal kapcsolatban, hogy mégis hogyan lehet egy nagyjából szabványosank tekinthető OpenID autentikációból távoli kódfuttatást kicsikarni.
1. OpenID
A történet természetesen az OpenID-val kezdődik. Az OpenID egy nyílt autentikációs protokoll, melynek segítségével egy-egy kiválasztott szolgáltató igazolhatja a felhasználók "digitális személyazonosságát" több más szolgáltató felé. Példaként az oldal jobb szélén díszeleg a StackExchange kitűzőm:
A StackExchange oldalára pedig bárki be tud lépni (OpenID terminológiában ő a Relaying Party) pl. Google vagy Facebook azonosítójával (ők az OpenID Providerek), mindezt úgy, hogy a Stack Exchange a Google/FB jelszavunkat soha nem érinti. Így egyszerűsödik a jelszómenedzsment és csökken a támadási felület, mindenki boldog.
Mivel az OpenID-t sok különböző szolgáltató használja, természetesen sok különböző megvalósítás is létezik, a problémák pedig hagyományosan itt kezdődnek. Reginaldo első lépésben nem is a Facebook iránt, hanem a népszerű OpenID megoldások iránt érdeklődött, és talált is hibát, először a Drupal megvalósításában, majd a Google szolgáltatásaiban is (ezért a cég 500 dolláros jutalmat ajánlott fel neki).
Mielőtt azonban a konkrét hiba részleteit taglalnánk lássuk, hogy hogyan is lehet az OpenID-t megetetni adatokkal! A felhasználónak (User-Agent) először is meg kell mondania a RP-nak, hogy OpenID-val szeretne belépni, ebben a lépésben pedig azt is közli a UA, hogy hol található a kívánatos OP. Az RP ez után elballag a megadott OP címére, és mindenféle adatokat kér tőle - hogy pontosan milyeneket, az a következő fejezetből derül ki, nekünk most arra érdemes odafigyelnünk, hogy a UA (vagyis esetünkben a támadó) a kezdeti kérés mellett az OP címét vagyis az OP által visszaadott adatokat is kontrollálja!
Vegyük észre továbbá, hogy Reginaldo esetében az Facebook nem OP, hanem RP szerepben van, a támadás az RP-ket célozza!
2. XML External Entity Injection
Miután megkapta az OP címét, az RP megkezdi az ún. discovery fázist, melyben lekérdezi az OP-től, hogy találhatók a tényleges szolgáltatás végpontok, ezek milyen verziójú OpenID protokollt támogatnak stb. Ezek az információk két féle forámban érkezhetnek: HTML-ben illetve XRDS-ben, utóbbi esetet nevezzük Yadis discovery-nek és lényegében egy XML alapú formátumról van szó.
Itt jön képbe az XML External Entity Injection: az XML entity-k (enetitások?) lényegében aliasok komplexebb (vagy nehezebben kifejezhető) struktúrákra, mindenki által ismert példák a < > &, melyek foglalt karaktereket helyettesíthetnek egy dokumentumban. Az entity-k behelyettesítését az XML feldolgozók általában automatikusan elvégzik. Az external entity-k annyiban különlegesek, hogy ez a behelyettesítés nem valamilyen elre definiált, szabványos módszer szerint történik, hanem a behelyettesítendő érték az XML dokumentumon kívüli, akár futtatókörnyezet, vagy operációs rendszer szinten elérhető adat alapján jön létre. Ilyen módon a következő dokumentumot (az OWASP-tól kölncsönöztem) feldolgozva az &xxe; entitás helyére a /dev/random "tartalma" kerül...
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///dev/random" >]><foo>&xxe;</foo>
... a folyamat (rosszabb esetben a rendszer) pedig memória hiányában összedől. Ha pedig a következő dokumentum tartalma feldolgozás után valahogy visszajut a felhasználóhoz, az /etc/passwd is kiolvashatóvá válik:
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>
3. PHP
De hogy lesz ebből kódfuttatás? A fenti két lépés az OpeinID wikioldala elolvasása után nagyjából kézenfekvő lehet annak, aki tudja mi az az XEE, de aki kicsit beleásott a témába, az azt is tudhatja, hogy az ezek a támadások általában nem túl erősek az XML szabvány szigorú megkötései (karakterkészlet, jól formázottság) miatt. A magam részéről ilyen problémákkal szinte kizárólag Java alkalmazásoknál találkoztam - mint tudjuk, a Java meg az XML kéz a kézben jár, a népszerű javás XML feldolgozóknál pedig alapértelmezetten engedélyezett a külső entitások feldolgozása (ellentétben pl. a .NET-tel) - és a sérülékenységek besorolása általában megragadt a sárga-közepes kockázatú kategóriában. De hála a Tervezőnek, XML-t feldolgozni PHP-val is lehet...
A PHP egyik biztonsági körökben közkedvelt lehetősége a különböző erőforrás wrapperek használata. Egy egyszerű URL lekérdezéskor pl. a 'http://' wrappert hívjuk segítségül, hogy hozzáférjünk a webes tartalomhoz, de a php:// wrapperen keresztül (megfelelő konfiguráció mellett) egy helyi file include probléma egy csapásra kódfuttatássá varázsolható.
A poén az, hogy ezek a wrapperek a XML entitások feldolgozásakor is működnek, így egy elegáns base64-el megoldhatjuk a formátumellenőrzésből fakadó problémákat:
<!ENTITY a SYSTEM 'php://filter/read=convert.base64-encode/resource=/etc/passwd'>
Az expect:// burkolóval pedig kódot is futtathatunk:
<!ENTITY a SYSTEM 'expect://id'>
Vége a játéknak.
Utóirat
Itt ragadnám meg az alkalmat, hogy (ismét) felhívjam a figyelmet az Offensive-Security összefoglalójára a saját bug bounty programjukkal kapcsolatos tapasztalataikról. Ajánlott olvasmány minden potenciális résztvevőnek, kiírónak, és hőbörgőnek!
|Z| 2014.01.29. 18:56:36