A deserializációs támadások - a szerializált adatok jogosulatlan manipulálása rosszindulatú kód futtatására - súlyos károkat okozhatnak a szervezeteknek. A fejlesztőknek és a biztonsági csapatoknak proaktívan kell azonosítaniuk a kulcsfontosságú sebezhetőségeket, amelyekből kiderül, hogyan, mikor és hol történt a támadás.
Ennek a fenyegetésnek egy friss példája a CVE-2023-34040, egy deserializációs támadási vektor a Springben az Apache Kafka számára, amely RCE (távoli kódfuttatáshoz) vezethet. A deserializációs sebezhetőségek elterjedtségét az OWASP Top 10-es listája is kiemeli, amely a "Megbízhatatlan adatok deserializálása" a 10 legkritikusabb webes alkalmazásbiztonsági kockázat között szerepel.
Az olyan eszközök, mint OPSWAT MetaDefender Core™ a SBOMSoftware Bill of Materials) motorjával elengedhetetlenek a deserializációs támadások felismerésében és megelőzésében. Ezek lehetővé teszik a fejlesztők számára, hogy hatékonyan vizsgálják és elemezzék kódjukat, biztosítva, hogy egyetlen sebezhetőséget sem hagynak figyelmen kívül.
Ebben a blogban diplomás ösztöndíjasaink a CVE-2023-34040 és kihasználásának részleteit, valamint a nyílt forráskódú komponensek hasonló fenyegetések elleni védelmét tárgyalják.
A CVE-2023-34040-ről
A CVE-2023-34040 felfed egy deserializációs támadási vektort a Spring for Apache Kafka-ban, amely szokatlan konfiguráció alkalmazása esetén kihasználható. Ez a sebezhetőség lehetővé teszi a támadó számára, hogy rosszindulatú szerializált objektumot építsen az egyik deserializációs kivételrekord fejlécében, ami RCE-t eredményezhet. A sebezhetőség a Spring for Apache Kafka 2.8.1-2.9.10 és 3.0.0-3.0.9 verzióit érinti.
Konkrétan az alkalmazás/fogyasztó a következő speciális konfigurációk és feltételek mellett sebezhető:
- A ErrorHandlingDeserializer osztály nincs konfigurálva a rekord kulcsához és/vagy értékéhez.
- A fogyasztó checkDeserExWhenKeyNull és/vagy checkDeserExWhenValueNull tulajdonságai true értékűek.
- A nem megbízható források közzétehetnek egy Kafka-témát.
Apache Kafka
Az Apache Software Foundation által kifejlesztett Apache Kafka egy olyan elosztott eseményáramlási platform, amelyet a különböző forrásokból, például adatbázisokból, érzékelőkből és mobile származó valós idejű adatfolyamok rögzítésére, feldolgozására, megválaszolására és továbbítására terveztek.
Például értesítéseket továbbíthat olyan szolgáltatásokhoz, amelyek reagálnak az ügyfél tevékenységeire, például a termékvásárlás befejezésére vagy a fizetés teljesítésére.
Az Apache Kafkában az esemény - más néven rekord vagy üzenet - olyan adategységként szolgál, amely az alkalmazásban egy eseményt képvisel, amikor adatot olvasnak vagy írnak. Minden esemény tartalmaz egy kulcsot, értéket, időbélyeget és opcionális metaadat fejléceket.
Kulcs-bináris (lehet nulla) | Érték-bináris (lehet nulla) | ||||
Tömörítési típus [nincs, gzip, snappy, lz4, zstd] | |||||
Fejlécek (opcionális)
| |||||
Partíció + Offset | |||||
Időbélyegző (rendszer vagy felhasználó által beállított) |
Az eseményeket tartósan tárolja és témákba rendezi. Azokat az ügyfélalkalmazásokat, amelyek eseményeket küldenek (írnak) a Kafka topikokba, producereknek nevezzük, míg az eseményeket előfizető (olvasó és feldolgozó) alkalmazásokat fogyasztóknak.
Spring az Apache Kafka számára
Az Apache Kafka és a Spring ökoszisztéma összekapcsolásához a fejlesztők használhatják a Spring for Apache Kafka-t, amely leegyszerűsíti az integrációt a Java alkalmazásokba.
A Spring for Apache Kafka megbízható eszközöket és API-kat kínál, amelyek leegyszerűsítik az események Kafka-val történő küldését és fogadását, lehetővé téve a fejlesztők számára, hogy ezeket a feladatokat kiterjedt és összetett kódolás nélkül hajtsák végre.
Szerializálás és deserializálás
A szerializálás egy olyan mechanizmus, amely egy objektum állapotát egy karakterlánccá vagy bájtfolyamba konvertálja. Ezzel szemben a deserializálás a fordított folyamat, amikor a szerializált adatokat visszaalakítjuk az eredeti objektummá vagy adatstruktúrává. A szerializálás lehetővé teszi az összetett adatok átalakítását, hogy azokat fájlba lehessen menteni, hálózaton keresztül elküldeni vagy adatbázisban tárolni. A szerializálás és a deszerializálás elengedhetetlen az elosztott rendszerekben történő adatcseréhez, és elősegíti a kommunikációt a szoftveralkalmazás különböző összetevői között. A Java-ban a writeObject() funkciót a szerializálásra, a readObject() funkciót pedig a deserializálásra használják.
Mivel a deserializálás lehetővé teszi egy bájtfolyam vagy karakterlánc objektummá alakítását, a bemeneti adatok nem megfelelő kezelése vagy a megfelelő érvényesítés hiánya jelentős biztonsági sebezhetőséget eredményezhet, ami potenciálisan RCE-támadáshoz vezethet.
Sebezhetőségi elemzés
A CVE leírása szerint OPSWAT Fellows a checkDeserExWhenKeyNull és a checkDeserExWhenValueNull értékeket igaznak állította be a biztonsági rés kiváltásához. Egy üres kulcs/értékkel rendelkező rekord elküldésével és részletes elemzéssel, a fogyasztó hibakeresésével, ahogyan az egy Kafka rekordot kapott a termelőtől, diplomás ösztöndíjasaink a következő munkafolyamatot fedezték fel a rekord feldolgozása során:
1. lépés: Rekordok (üzenetek) fogadása
A rekordok fogadásakor a fogyasztó meghívja az invokeIfHaveRecords() metódust, amely ezután meghívja az invokeListener() metódust, hogy egy regisztrált rekordhallgatót (a @KafkaListener annotációval annotált osztály) indítson el a rekordok tényleges feldolgozására.
Az invokeListener() ezután meghívja az invokeOnMessage() metódust.
2. lépés: A rekordok ellenőrzése
Az invokeOnMessage() metóduson belül több feltétel kerül kiértékelésre a rekordérték és a konfigurációs tulajdonságok alapján, amelyek ezt követően meghatározzák a következő végrehajtandó lépést.
Ha egy rekordnak nulla kulcsa vagy értéke van, és a checkDeserExWhenKeyNull és/vagy checkDeserExWhenValueNull tulajdonságok kifejezetten igazra vannak állítva, a checkDeser() metódus meghívásra kerül a rekord vizsgálatára.
3. lépés: Kivétel ellenőrzése a fejlécekből
A checkDesr() funkcióban a fogyasztó folyamatosan meghívja a getExceptionFromHeader() funkciót, hogy lekérje a rekord metaadataiból a kivételeket, ha vannak ilyenek, és az eredményt egy exception nevű változóban tárolja.
4. lépés: Kivétel kivonása a fejlécekből
A getExceptionFromHeader() metódus célja, hogy kivonjon és visszaadjon egy kivételt a Kafka rekord fejlécéből. Először lekérdezi a rekord fejlécét, majd megszerzi a fejléc értékét, amely egy bájt tömbben van tárolva.
Ezt követően a fejléc értékének bájt tömbjét továbbítja a byteArrayToDeserializationException() metódusnak további kezelésre.
5. lépés: Az adatok deserializálása
A byteArrayToDeserializationException() függvényben a resolveClass() függvényt felülírja, hogy a deserializációt csak az engedélyezett osztályokra korlátozza. Ez a megközelítés megakadályozza minden olyan osztály deserializálását, amely nem kifejezetten engedélyezett. A fejléc byte tömb értéke csak akkor deserializálható a byteArrayToDeserializationException() függvényen belül, ha megfelel a resolveClass() függvényben meghatározott feltételnek, amely kizárólag a DeserializationException osztály számára engedélyezi a deserializálást.
A DeserializationException osztály azonban kiterjeszti a szabványos Exception osztályt, és tartalmaz egy konstruktort négy paraméterrel. Az utolsó paraméter, az ok, a DeserializationExceptiont kiváltó eredeti kivételt, például egy IOExceptiont vagy egy ClassNotFoundExceptiont jelöli.
A Throwable osztály a Java-ban kivételként vagy hibaként dobható összes objektum szuperosztályaként szolgál. A Java programozási nyelvben a kivételkezelő osztályok, mint a Throwable, Exception és Error biztonságosan deserializálhatók. Egy kivétel deserializálásakor a Java lehetővé teszi a Throwable osztályok szülőosztályainak betöltését és példányosítását a normál osztályoknál alkalmazott ellenőrzéseknél kevésbé igényes ellenőrzésekkel.
A munkafolyamat és az átfogó elemzés alapján, ha a szerializált adatok egy olyan rosszindulatú osztálynak felelnek meg, amely a Throwable szülőosztályból örököl, akkor megkerülheti a feltételellenőrzéseket. Ez lehetővé teszi egy rosszindulatú objektum deserializálását, amely kártékony kódot hajthat végre, és potenciálisan RCE-támadást eredményezhet.
Kihasználás
Ahogy az elemzésben is szerepel, a sebezhetőség kihasználásához rosszindulatú adatokat kell generálni, amelyeket a Kafka fejléc rekordján keresztül küldenek a fogyasztónak. A támadónak kezdetben egy rosszindulatú osztályt kell létrehoznia, amely a Throwable osztályt bővíti, majd egy gadget-láncot kell felhasználnia a távoli kódfuttatás eléréséhez. A gadgetek kihasználható kódrészletek az alkalmazáson belül, és ezek láncolatával a támadó elérhet egy "elnyelő gadgetet", amely káros műveleteket indít el.
A következő egy rosszindulatú osztály, amely felhasználható a Spring for Apache Kafka biztonsági rés kihasználására:
Ezután létrehozzuk a malicious osztály egy példányát, és átadjuk a DeserializationException osztály konstruktorának cause paraméteréhez. A DeserializationException példányt ezután egy bájtfolyamba szerializáljuk, amelyet ezt követően a rosszindulatú Kafka rekord fejlécének értékeként használunk.
Ha a támadó sikeresen ráveszi az áldozatot, hogy használja a rosszindulatú termelőjét, akkor irányíthatja a fogyasztónak küldött Kafka rekordokat, lehetőséget teremtve a rendszer kompromittálására.
Amikor a sebezhető fogyasztó egy olyan Kafka rekordot kap a rosszindulatú termelőtől, amely null kulcsokat és értékeket tartalmaz, valamint egy rosszindulatú szerializált példányt a rekord fejlécében, a fogyasztó feldolgozza a rekordot, beleértve a deserializációs folyamatot is. Ez végül távoli kódfuttatáshoz vezet, ami lehetővé teszi a támadó számára a rendszer kompromittálását.
A CVE-2023-34040 enyhítése a MetaDefender Core SBOM segítségével
A CVE-2023-34040-hez kapcsolódó kockázatok hatékony csökkentéséhez a szervezeteknek olyan átfogó megoldásra van szükségük, amely átláthatóságot és ellenőrzést biztosít a nyílt forráskódú komponensek felett.
Az SBOM, a MetaDefender Core egyik alaptechnológiája, hatékony választ nyújt. Azáltal, hogy az SBOM az összes használt szoftverkomponens, könyvtár és függőség átfogó leltáraként működik, lehetővé teszi a szervezetek számára, hogy proaktív és hatékony módon nyomon kövessék, biztosítsák és frissítsék nyílt forráskódú komponenseiket.
Az SBOM segítségével a biztonsági csapatok:
- Gyorsan megtalálja a sérülékeny alkatrészeket: Azonnal azonosítsa a deserializációs támadások által érintett nyílt forráskódú komponenseket. Ez biztosítja a gyors intézkedést a sebezhető könyvtárak foltozása vagy cseréje terén.
- Proaktív javítások és frissítések biztosítása: Folyamatosan figyelje a nyílt forráskódú komponenseket az SBOM-on keresztül, hogy megelőzze a deserializációs sebezhetőségeket. Az SBOM képes észlelni az elavult vagy nem biztonságos komponenseket, lehetővé téve az időben történő frissítéseket és csökkentve a támadásoknak való kitettséget.
- A megfelelés és a jelentéstétel fenntartása: Az SBOM segít a szervezeteknek megfelelni a megfelelési követelményeknek, mivel a szabályozási keretek egyre inkább előírják a szoftverellátási láncok átláthatóságát.
Záró gondolatok
A deserializációs sebezhetőségek jelentős biztonsági fenyegetést jelentenek, amelyek alkalmazások széles körének kihasználására használhatók. Ezek a sebezhetőségek akkor lépnek fel, amikor egy alkalmazás rosszindulatú adatokat deserializál, lehetővé téve a támadók számára tetszőleges kód futtatását vagy érzékeny információkhoz való hozzáférést. A CVE-2023-34040 biztonsági rés a Spring for Apache Kafka-ban élesen emlékeztet a deserializációs támadások veszélyeire.
A deserializációs támadások megelőzése érdekében elengedhetetlen az olyan fejlett eszközök, mint az OPSWAT MetaDefender Core és annak SBOM technológiája. A szervezetek mély rálátást nyerhetnek szoftverellátási láncukra, biztosíthatják a sebezhetőségek időben történő foltozását, és védekezhetnek a folyamatosan változó fenyegetésekkel szemben. A nyílt forráskódú komponensek proaktív védelme nem csupán a legjobb gyakorlat - a modern rendszerek védelméhez a potenciális kizsákmányolás ellen elengedhetetlen.