6 min čteníJohnny UnarJohnny Unar

Váš pipeline na optimalizaci obrázků je vstupní bod pro vzdálené spuštění kódu

Ta AVIF CVE v Next.js vlastně v Next.js nebyla. Seděla čtyři úrovně hluboko v závislostech, v kódu, který nikdo z vašeho týmu nikdy nečetl, a spouštěla se při každém requestu na obrázek.

CVE, které nikdo nenapsal

25. srpna 2026 Vercel zveřejnil zranitelnost s CVSS 9.5, která postihovala optimalizaci obrázků v Next.js. Kdybyste přečetli jen nadpis advisory, řekli byste si, že někdo z Vercelu prostě shipnul bug v next/image. Jenže ne. Zranitelnost byla v libheif, knihovně na dekódování AVIF a HEIC, která se tam dostane tranzitivně přes sharp, a ten next/image volá potichu při každém jednom optimalizačním requestu, pokud jste to explicitně nevypnuli. Řetězec vypadá takhle: útočník pošle upravený AVIF soubor na váš /_next/image endpoint, Next.js ho předá sharpu, sharp ho předá libheif, a libheif má v parsování boxů heap overflow, který útočníkovi umožní přepsat paměť ve stejném procesu, který obsluhuje vaši aplikaci. Patchnete na 15.5.24 nebo 16.3.3 a advisory se zavře, ale zajímavá není ta záplata. Zajímavé je, kolik vrstev kódu spouštíte nad daty ovládanými útočníkem, aniž byste se do jediné z nich kdy podívali. Lidi, co ten bug našli, skoro určitě pouštěli na parser boxů v libheif fuzzing s pomocí LLM, protože přesně tenhle typ hluboko zanořeného, formátově specifického overflow dřív zabral výzkumníkovi týdny, než ho izoloval, a dneska vypadne z jednoho odpoledne automaticky generovaných harnessů. Ta časová osa je důležitá a ještě se k ní vrátím.

proč „my používáme známou knihovnu" není obrana

Pokaždé, když něco takového dopadne, někdo v incident kanálu řekne nějakou variaci na „ale sharp je přece prověřený, je to standard, používá ho každý". Tahle obrana je k ničemu a stojí za to přesně říct proč. sharp je fakt dobře udržovaný. libvips pod ním je dobře udržovaný. Problém je, že ani jeden z nich neovládá libheif, a libheif je C++ knihovna, co parsuje formát ISO base media file, který byl navržený pro video kontejnery a pak se nasrafoval na statické obrázky. To znamená, že s sebou nese obrovskou plochu box typů, extentů a referenčních struktur, které útočník může zanořovat a překrývat způsoby, jaké původní autoři nikdy pořádně neotestovali. Když říkáte „věříme sharpu", ve skutečnosti říkáte „věříme sharpu, a libvips, a libheif, a libde265, a čemukoli, na čem tohle tranzitivně závisí, navždycky, včetně budoucích verzí, do kterých se automaticky updatneme". Důvěra nekončí na hranici, kterou vidíte. Jakýkoli veřejný endpoint, který dekóduje média dodaná útočníkem, je defaultně kandidát na RCE, a počet známých knihoven v řetězci není polehčující okolnost. Je to míra toho, kolik nezávislých týmů nesmí nikdy udělat chybu v memory safety, abyste vy zůstali v bezpečí. To není sázka, kterou bych chtěl uzavírat u Django nebo Next.js aplikace vystavené na otevřeném internetu, a u klientů, kterým provozujeme infrastrukturu, jsme s ní přestali.

audit vlastního pipeline

Začněte tím, že zjistíte, jestli vaše Next.js aplikace sharp vůbec volá. Pokud jedete na managed optimalizaci obrázků od Vercelu, dekódování běží na jejich infrastruktuře a blast radius je jejich, ne váš. Pokud si to hostujete sami, a spousta z vás, co tohle čte, provozuje Next.js 15.x nebo 16.x na vlastních ECS taskách nebo holých Hetzner strojích, tak next/image spouští sharp ve vašem procesu a expozice je na vás. Mrkněte do next.config.js na blok images. Pokud jste explicitně neomezili formáty, next/image vesele přijme a dekóduje AVIF, což je přesně cesta, kterou tohle CVE využívá. Můžete nastavit formats: ['image/webp'] a zahodit AVIF output, ale pozor, tohle řídí to, co Next.js produkuje, ne nutně každou dekódovací cestu pro to, co konzumuje. Bezpečnější je omezit, co přijímáte, na edge. Pokud vaši uživatelé nahrávají obrázky a ty se kdekoli ve stacku dostanou k sharpu, berte ten pipeline jako nedůvěryhodné spouštění kódu a zavřete ho za hranici. U pár klientů, co provozují document processing pipeliny, jsme tohle dekódování přesunuli do samostatného workeru: malá Go služba, která volá zocelený sharp proces s memory limitem a bez síťového egressu, takže úspěšný overflow vám sestřelí sandbox, ne že dostane shell na stroji, kde leží Postgres credentials. Taky si reálně pusťte npm ls sharp a npm ls libheif nebo ekvivalent, protože půlka týmů, se kterými jsem o tom mluvil, ani netušila, že mají sharp ve stromě, dokud se nepodívala.

co signalizuje měsíční bezpečnostní kadence

Vercel letos potichu přešel na měsíční kadenci bezpečnostních releasů, a nemyslím, že dost lidí došlo, co to vlastně znamená. Pravidelné bezpečnostní releasy si neplánujete proto, že jste najednou zlajdačeli. Plánujete si je proto, že rychlost příchozích disclosures překročila hranici, kde ad-hoc patchování přestalo škálovat, a ta hranice se překročila, protože se výzkum zranitelností automatizuje. Ten overflow v libheif je učebnicový příklad bugu, jaký fuzzing s pomocí LLM odhaluje ve velkém: parsery formátů s hlubokým stavem, spoustou hraničních případů a desítkami let nabaleného kódu, který nikdo neproauditoval od začátku do konce. Výzkumníci teď generují fuzz harnessy, triážují cracky a dokonce sepisují reprodukce s pomocí modelu, což smrskává čas objevu z týdnů na hodiny a násobí počet lidí, co to zvládnou. Důsledek pro vás je, že se mezera mezi „tenhle bug existuje ve tvém dependency stromě" a „tenhle bug je veřejný a lidi po něm skenují" rychle zmenšuje, a čtvrtletní rituál bumpování závislostí už není dost rychlý. Pokud automaticky skenujete Shodan na vystavené Next.js image endpointy, a lidi to dělají, okno mezi disclosure s CVSS 9.5 a masovými pokusy o exploit se dneska měří ve dnech. Podle toho si naplánujte patch kadenci, ne podle starých představ o tom, kolik máte času.

co reálně udělat tento týden

Patchnout na 15.5.24 nebo 16.3.3, to je jasné, a udělejte to dneska, pokud je vaše aplikace veřejná a dekóduje nahrané nebo vzdálené obrázky. Tím zavřete bezprostřední díru. Pak přijde ta těžší práce: rozhodnout se, jestli vůbec chcete dál provozovat dekodéry médií ve stejném procesu a privilegovaném kontextu jako logiku aplikace. Můj upřímný názor je, že nechcete, u ničeho vystaveného na internet, protože další bug ranku libheif už teď sedí v nějakém kodeku ve vašem stromě a čeká, až ho automatický fuzzer najde, a záplata, kterou nasadíte příští měsíc, vám nepomůže proti té, co se zveřejní o měsíc později. Přesuňte dekódování za hranici procesu s memory capem, bez secretů a bez sítě. Omezte formáty, které přijímáte, na ty, co reálně potřebujete. Vypněte příjem AVIF, pokud na něm nic ve vašem produktu nestojí, protože formát, který nepřijímáte, je parser, který nikdy nezavoláte. A postavte si reálnou deploy cestu pro bezpečnostní záplaty, aby shipnutí point releasu byla patnáctiminutovka, ne dvoudenní change-management sága, protože kadence těchhle disclosures jede jen jedním směrem. Pokud chcete druhý pár očí na váš image pipeline nebo obecně na expozici v závislostech, přesně takový bezpečnostní audit ve steezru děláme, a obvykle se zaplatí hned napoprvé, kdy zabrání tomu, aby se z upraveného AVIF stal shell na vaší infrastruktuře.

Johnny Unar

Napsal/a

Johnny Unar

Chcete s námi spolupracovat?

Ta AVIF CVE v Next.js vlastně v Next.js nebyla. Seděla čtyři úrovně hluboko v závislostech, v kódu, který nikdo z vašeho týmu nikdy nečetl, a spouštěla se při každém requestu na obrázek.