Portál AbcLinuxu, 4. května 2025 13:48
Několik oblíbených aplikací založených na Javě je zasaženo závažnou zranitelností, která může být využita útočníkem ke vzdálenému spuštění libovolného kódu. Chyba v oblíbené Java knihovně Apache Commons Collections se týká mimo jiné i produktů Oracle WebLogic, IBM WebSphere, Red Hat JBoss, Jenkins, nebo OpenNMS. [CSIRT.CZ]
Tiskni
Sdílej:
java.io.Serializable
na data, která může útočník změnit. A největší podíl na té chybě pak má asi třída sun.reflect.annotation.AnnotationInvocationHandler
, která při deserializaci volá kód deserializovaných tříd. Princip té chyby je vysvětlený např. v diskusi na Rootu – ten originální popis zranitelnosti odkazovaný ve zprávičce je zmatený a velmi neúplný.
select * from student; exec('touch /tmp/hacked')
serializace v Jave umoznuje serializovat cele klasyJste si tím jist? Samozřejmě pokud vynecháme různé triky s classloadery – což je ale vlastnost těch classloaderů a ne serializace.
RMI potazmo serializace je protokol, ktery urcen pro komunikaci pro v ramci interni siteOpravdu je dobré rozlišovat serializace (obecnou) a serializaci Java objektů definovanou ve specifikaci jazyka a reprezentovanou rozhraním
java.io.Serializable
. Potenciálně nebezpečná je každá deserializace, ale udělat bezpečně tu Java-deserializaci je skoro nemožné, zatímco udělat bezpečně deserializaci dat odesílaných klasicky z webového formuláře zvládne skoro každý, pokud není úplný hlupák nebo programátor PHP redakčních systémů.
pokud se zmocnim stroje, kde bezi klientV případě aplikace s tlustým klientem to může být kdokoli s tím tlustým klientem, případně další lidé, kteří jsou třeba ve stejné síti. No a k těm aplikačním serverům často bývají tlustí klienti, což je přesně ten postup, kterým ty aplikační servery napadli. Takže zas tak bezzubé to není.
Mozna to je take duvod pro to nikdo "neopravuje".On „to“ nikdo neopravuje především proto, že příčina je úplně někde jinde, než plyne z toho oznámení. A skutečná oprava bude nejspíš znamenat použít jiný protokol, což není záležitost na pár dní.
Necet jsem ten clanek uplne do superpodrobnostiPodle mne o nic nepřijdete, když ho číst nebudete – přečtěte si radši ten komentář na Rootu, který jsem odkazoval. Tam je podstata problému vysvětlená, na rozdíl od toho oznámení od FoxGlove, kde se vysvětlení podstaty problému úspěšně vyhnuli a ze tří postupně navazujících podmínek, které musí být splněny, aby bylo možné útok provést, jmenují jenom jeden konkrétní případ té třetí (knihovna Apache Commons Collections), přičemž podobné útoky půjde udělat i s jinými třídami.
udělat bezpečně tu Java-deserializaci je skoro nemožné
pokud není úplný hlupák nebo programátor PHP redakčních systémů.Nebo ucitel telocviku
System.exit()
nebo Runtime.exec()
, z aplikačního kontextu může jít vyzvednout libovolný objekt, třeba databázové spojení…).
Runtime.exec
můžete sestavit z Class.forName()
, Class.getMethod()
a Method.invoke()
. Tyhle třídy jsou dostupné dokonce v runtime knihovně. Ty by se také měly odstranit? Takhle můžeme pokračovat dál, musel byste odstranit celou reflexi a vše týkající se classloaderů. Minimálně.
osobne by me nenapadlo, ze v knihovne pro kolekce podobna zalezitost vubec budePodobné věci najdete ve spoustě dalších knihoven. Chyba není v těch knihovnách, ale ve vaší domněnce, že je to nějak výjimečný kód.
Tzn. z meho pohledu se jedna o navrhovou chybu v knihovneTo je ale váš chybný pohled, Java nikde nezaručuje, že budete mít běhové prostředí, ve kterém můžete zavolat libovolný kód a bude to bezpečné. Obrovské množství nebezpečných tříd máte přímo v běhové knihovně (
rt.jar
). Pro zajištění bezpečného prostředí v Javě můžete použít bezpečnostní politiku (java.lang.SecurityManager
), ta vám umožní to bezpečné běhové prostředí vytvořit. Používá se to třeba v Java Pluginu (applety a Web Start). Používají to i aplikační servery, ale obvykle k ochraně aplikačního serveru před nainstalovanými aplikacemi – tato chyba ukázala, že by by měl být aplikační server chráněn i před vlastní administrací…
LazyMap
funguje tak, že dostane klíč, zavolá na něm příslušný transformer a jeho výsledek vrátí jako hodnotu k tomu klíči. Co vám na tom připadá nesmyslného, co jiného by měla dělat LazyMap? Ta samé dělá třeba metoda Stream.map()
v Javě 8. InvokerTransformer
funguje tak, že má zadán název nějaké metody, a transformaci provádí tak, že na vstupu dostane nějaký objekt, přes reflexi na něm zavolá tu zadanou metodu, a její výstup vrátí jako výstup transformace. Co je na tomhle transformeru nesmyslného?
Ovsem pak musi domyslet, co tomuze mit za dopady, a to enni lehkeTen transformer je v podstatě jen omáčka okolo
Class.getMethod().invoke()
. Takže pokud by se někdo měl zamýšlet nad dopady, museli by to být autoři tříd Class
a Method
. Přičemž takovéhle dopady mají třeba desítky nebo stovky metod jenom z runtime knihovny. Co byste s tím chtěl dělat? Redukovat celou Jav na dvě bezpečné třídy Integer a Long?
opravdu tezko sestavite podobny exploit aplikace jen na zaklade toho, ze existuje Runtime.exec - nebo ano?Základem toho exploitu je třída
sun.reflect.annotation.AnnotationInvocationHandler
, která při deserializaci volá metodu get()
deserializovaných objektů. To je to klíčové místo, kde se z deserializace stane vykonávání kódu. Ty třídy z ACC jsou pak už jenom nástrojem, jak z volání Map.get()
udělat volání jiné metody. Ale implementací různých map, které půjde zneužít podobným způsobem, bude spousta.
A spousta bude i tříd jako AnnotationInvocationHandler
, které při deserializaci volají nějaký kód. Potenciálně nebezpečná je každá třída, která implementuje některé z rozšiřujících metod pro deserializaci, protože každá z nich může vyvolávat nějaký kód – a většina z nich to nejspíš dělá, jinak by nebyly potřeba.
Když dělám protokol na komunikaci mezi klientem a serverem (masterem a slavem, čímkoli), nekontrolovat vstupy mi připadá jako absolutní hazard. A to je přesně to co zmiňované aplikace dělají.Podle mne jsou ty chyby způsobené tím, že si autoři toho kódu neuvědomili, že jim útočník může do vstupu vložit jinou třídu, která napáchá škody už během deserializace. Útočníkovi už pak může být jedno, že celá deserializace nakonec skončí
ClassCastException
(pokud se ta deserializace vůbec dokončí).
Jinak ta Java serializace se většinou používá v případech, kdy těch serializovaných typů může být spousta a jejich množina je předem neznámá (RMI, JMX). Takže si moc nedovedu představit, že by se tam dělal nějaký white-list povolených tříd. I když asi by bylo možné kontrolovat, zda načítaná třída neimplementuje rozšířené metody pro deserializaci, a udělat white-list teprve těch tříd, které ty rozšířené metody implementují a přesto je jejich deserializace za všech okolností bezpečná. Ale upřímně řečeno, takovouhle implementaci měl mít už ObjectInputStream
, a ten současný ObjectInputStream
měl být jeho předek pojmenovaný UnsafeObjectInputStream
. Nebo něco takového. Protože se nedá očekávat, že každého napadne, že aby to bylo bezpečné, musí správně přetížit metody resolveClass()
a resolveProxyClass()
. Navíc ty metody ani neumožňují vyhodit SecurityException
, takže s jejich použitím pro kontrolu vstupu nejspíš nepočítali ani sami autoři Javy.
LazyMap lze obecne realizovat klasickymi callbacky a tam vam svuj kod nikdo nepodstrci, protoze se nikam neserializuje.LazyMap z ACC je realizováno klasickými callbacky a nikam se neserializuje.
Coz je zde podstata problemuOpravdu nemá smysl bavit se o tom konkrétním exploitu a třídách z ACC, když nevíte ani základní princip fungování toho exploitu ani jste se nepodíval na zdrojáky těch tříd z ACC.
pro exploit je vedle jineho potreba mit k dispozici tridu, ktera je "ochotna" zavolat reflexi kod, ktery se ji pripravi v serializovane forme, tzn. nikdy nebyl v zadne forme pritomen v puvodni aplikaci.Třeba třídy
java.lang.Class
a java.lang.Method
. Už jsem se vás ptal, co chcete dělat s nimi.
Znate jine tridy, jejichz metoda get() ma podobne vlastnosti?Vím, že metoda
LazyMap.get()
tyhle vlastnosti nemá. Tady máte její kód, abyste se na něj konečně podíval:
protected final Transformer super K, ? extends V> factory; @Override public V get(final Object key) { // create value for key if key is not currently in the map if (map.containsKey(key) == false) { @SuppressWarnings("unchecked") final K castKey = (K) key; final V value = factory.transform(castKey); map.put(castKey, value); return value; } return map.get(key); }
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.