stejná chyba, třikrát po sobě
CVE-2026-15013, CVE-2026-57807 a CVE-2026-61979 přišly během tří měsíců, všechny míří na WordPress SSO pluginy, a když si ta oznámení přečtete jedno po druhém, začnete mít pocit, že čtete pořád to samé disclosure, jen s prohozeným číslem CVE. Jiné pluginy, jiní dodavatelé, identická chyba. Ve všech případech si plugin vytáhl nějaké pole z příchozí SAML nebo OAuth odpovědi, pole, které útočník plně ovládá, protože si tu odpověď sám vytváří, a tomu poli věřil při rozhodování, jak zprávu ověřit, místo aby vynutil to, co si admin nastavil lokálně. Výsledkem bylo pokaždé převzetí admin účtu bez přihlášení. Ne eskalace oprávnění z účtu s nízkými právy. Ne něco, co potřebuje phishing. Prostě POSTnete zfalšovanou assertion na ACS endpoint a jste ve wp-adminu.
Signature confusion je klasická varianta tohohle. SAML response je XML, může nést podpis a ten podpis může sedět na úrovni response nebo na úrovni assertion. Líný verifier zkontroluje ten podpis, na který zrovna narazí první, nebo hůř, zkontroluje, že někde v dokumentu existuje platný podpis, ale neověří, že skutečně pokrývá tu assertion, jejímuž obsahu se chystáte věřit. Takže vezmete legitimně podepsanou assertion od bezvýznamného uživatele, zabalíte ji, vložíte novou nepodepsanou assertion, která tvrdí, že jste admin@obet.cz, a verifier ověří podpis, který vidí, a identitu si přečte z části, kterou nikdo nepodepsal. XML Signature Wrapping je zdokumentovaný zhruba od roku 2012. Není to žádný nový útok. A pořád se shipuje.
OAuth varianta u CVE-2026-61979 je bratranec tohohle: plugin si přečetl algoritmus z JWT hlavičky, viděl v alg něco, co by přijímat neměl, a podle toho ověřoval. Přitom celý smysl lokálně nastaveného vztahu důvěry je v tom, že algoritmus určujete vy, ne token.
proč jde o selhání protokolu, ne o údržbu
Nejjednodušší výklad tří CVE za 90 dní je, že WordPress pluginy jsou špatně udržované a měli byste prostě rychleji updatovat. Ten výklad je pohodlný a je špatně, nebo minimálně míjí to podstatné. SAML je fakt těžký protokol na správnou implementaci. Specifikace je obrovská, XML kanonikalizace je minové pole, model podepisování má dvě platná místa pro podpis a několik platných transformací, a počet způsobů, jak validaci nenápadně zkazit, mnohonásobně převyšuje počet způsobů, jak ji udělat správně. Každý tým, co píše SAML verifier, řeší znova problém, na kterém si vylámaly zuby security týmy ve firmách mnohem větších, než je nějaká dílna na WordPress pluginy.
Když nainstalujete SSO plugin, nepřidáváte feature. Outsourcujete to nejkritičtější bezpečnostní rozhodnutí, které vaše aplikace dělá, totiž „kdo je tenhle člověk a mám mu věřit", na závislost, jejíž autor musel správně implementovat protokol, na kterém padají i specialisté. Zdědíte jeho výklad specifikace, jeho volbu XML knihovny, jeho rozhodnutí, kde kontrolovat podpis, a jeho předpoklady o tom, kterým polím se dá věřit. Pokud v čemkoli udělal chybu, a statistika říká, že spousta implementací něco zkazí, máte tu chybu ve svém login flow. A je to typ chyby, který se nezhroutí elegantně. Autentizace buď drží, nebo předá klíče.
V tomhle je rozdíl mezi delegováním něčeho jako zmenšování obrázků a delegováním autentizace. Když má resizer obrázků bug, dostanete rozbitý náhled. Když má vrstva autentizace signature confusion bug, dostanete útočníka s platnou admin session a bez jediného záznamu v logu, který by vypadal divně, protože z pohledu aplikace je úspěšný zfalšovaný login nerozeznatelný od úspěšného skutečného. Ta asymetrie je celý argument. Blast radius chyby ve vaší autentizační závislosti je totální, takže laťka na to, co jste ochotni delegovat, by měla být mnohem výš než u čehokoli jiného ve vašem stacku.
co se ve verifieru skutečně pokazí
Vyplatí se být konkrétní ohledně mechaniky, protože „správně ověřte podpis" zní samozřejmě přesně do chvíle, než zíráte do configu python-saml nebo ruby-saml a snažíte se zjistit, která z patnácti voleb ovládá to, na čem vám záleží. Ta tři selhání spadají do několika kategorií.
Zaprvé, kontrola špatného podpisu. Verifier potvrdí, že podpis existuje a je platný, ale neváže ho na konkrétní assertion, jejíž subject a atributy se chystá číst. Oprava: ověřte, že podpis pokrývá přesně ten element, ze kterého identitu čtete, odmítněte odpovědi, kde podepsaný element a čtený element nejsou stejná node, a odmítněte dokumenty s víc assertions, než čekáte.
Zadruhé, důvěra útočníkem ovládaným metadatům při výběru parametrů ověření. Čtení alg z JWT hlavičky nebo čtení issueru z odpovědi a jeho použití k výběru klíče, kterému věřit, znamená, že útočník si vybírá, jak ho zkontrolujete. Váš kód by měl algoritmus i podepisovací klíč napevno vzít z lokální konfigurace a při jakémkoli nesouladu tvrdě spadnout. Pokud token tvrdí, že alg je none nebo HS256, když jste nastavili RS256, není to fallback, je to útok. Správná odpověď je 400 a řádek v logu, nikdy pokus o ověření útočníkovým schématem.
Zatřetí, nevalidování těch nudných polí. Audience restriction, okna NotBefore a NotOnOrAfter, Recipient u subject confirmation, InResponseTo, které odpovídá requestu, jenž jste skutečně poslali, a ochrana proti replay na assertion ID. Každé z tohohle je kontrola, kterou uspěchaná implementace vynechá, protože happy path funguje i bez ní, a každé z toho je nosné. Zfalšovaná assertion, která by neprošla validací audience, projde bez problému, pokud audience nikdy nekontrolujete.
vlastní integrace v Djangu nebo Next.js
Když ve steezru stavíme zákaznické portály a SaaS login, výchozí volba je integrovat SSO sami proti prověřené knihovně s jediným účelem, ne tahat dovnitř balík, co slibuje udělat všechno. V Djangu to znamená protáhnout SAML flow přes udržovanou knihovnu, kde explicitně nastavíte certifikát IdP, entity ID a algoritmus, a pak si napíšete vlastní tenkou view, která assertion přijme, předá ji verifieru a dokud ověření neproběhne čistě, s poli z odpovědi neudělá vůbec nic.
Podstatný tvar vypadá takhle. Podepisovací certifikát IdP načtete z vlastní konfigurace, ne z odpovědi. Přijímaný algoritmus nastavíte na pevnou hodnotu a knihovnu nakonfigurujete tak, aby cokoli jiného odmítla, takže RS256 znamená RS256 a dokument tvrdící něco jiného spadne. Zapnete strict mode, který ve většině slušných SAML knihoven zapne přesně ty kontroly audience, časování a destination, na které lidi jinak zapomínají. Pak ve view vytáhnete NameID a atributy až po úspěšné validaci, namapujete je na uživatele přes explicitní allowlist atributů, o které stojíte, a nikdy automaticky nezaložíte admina. Když assertion tvrdí roli, kterou vaše aplikace nezná, login zahodíte, nehádáte.
U Next.js aplikace platí ty samé principy přes knihovnu jako Auth.js, ale past je jiná. Je lákavé věřit id_token claimům rovnou z odpovědi providera, jenže u OIDC musíte podpis tokenu ověřit proti zveřejněné JWKS providera, napevno určit očekávaný algoritmus, zvalidovat iss a aud proti nastaveným hodnotám a zkontrolovat nonce proti tomu, který jste si uložili na serveru. Kontrola nonce je ta, kterou lidi vynechávají, a jejím vynecháním znovu otevřete dveře replay útoku, o kterých jste si mysleli, že je framework zavřel.
Kódu je na tohle málo. Verify funkce, config blok s certifikátem a napevno daným algoritmem a mapovací funkce uživatele, která při chybě spadne. Dost málo na to, abyste to celé přečetli na jedno posezení, což je celý smysl, protože verifier v pluginu nezauditujete tak jako čtyřicet řádků, které jste napsali sami.
jak zauditovat to, co už máte
Pokud SSO provozujete dneska, ať je to WordPress plugin nebo vlastní integrace, existuje krátká sada otázek, které vám řeknou většinu toho, co potřebujete vědět, aniž byste četli zdroják. Pošlete si login a zachyťte syrovou SAML response nebo id_token. Teď s tím zkuste manipulovat. Změňte claim, odstraňte podpis, přehoďte algoritmus na none, vložte druhou assertion, posuňte časové okno do minulosti, namiřte audience na jinou službu. Pokud kterákoli z těch zmanipulovaných zpráv vyprodukuje platnou session, máte nález, a nejspíš dost vážný.
Zeptejte se, odkud se bere podepisovací klíč. Pokud odpověď zahrnuje cokoli čteného z příchozí zprávy, je to vzorec CVE-2026-61979 a je potřeba to opravit hned. Zeptejte se, jestli je zapnutý strict mode. Zeptejte se, jestli existuje ochrana proti replay, protože spousta jinak správných implementací zvaliduje všechno kromě opětovného použití assertion ID a jsou úplně otevřené replay zachycené odpovědi. Zeptejte se, co se stane u role, kterou aplikace nezná, a pokud je odpověď „založí uživatele s výchozími oprávněními", zjistěte, jestli výchozí plus zfalšovaný atribut někde nedává dohromady admina.
Nepříjemný závěr z těch tří CVE je, že „používáme známý SSO plugin" není bezpečnostní postoj, ale sázka na to, že někdo, koho jste nikdy nepotkali, správně implementoval protokol pod komerčním časovým tlakem. Někdy ta sázka vyjde. Třikrát za devadesát dní nevyšla, a downside byl pokaždé plná administrátorská kompromitace bez phishingu, bez krádeže přihlašovacích údajů a bez jediné anomálie v logu. U všeho, kde vám útočník s admin účtem zničí den, si verifier vlastněte sami, držte ho malý a ať při chybě spadne. To je padesát řádků kódu, které ovládáte, proti neomezené odpovědnosti, kterou neovládáte.
