Neautenizovaný leak admin hesla v ZTE ZXHN H267A
Obsah · 13
CVE: TBD
ISP: O2
Vendor: ZTE
Model: ZTE ZXHN H267A
Verze firmwaru: V1.0.5_CZ.2T19
Status: EOL zařízení, ISP potvrdil chybu
Úvod
Moje první zranitelnost, kterou jsem našel a taky publikoval, mě donutila dívat se na zařízení, která používám, o dost skeptičtěji a aktivně v nich hledat security bugy. A vyplatilo se to. Pustil jsem se do dalšího routeru, který mi doma běží, a tentokrát jsem narazil na chybu podstatně závažnější než tehdejší podmíněné AFR (Arbitrary File Read).
Konkrétně jde o ZTE ZXHN H267A - router, který svým zákazníkům poskytuje ISP, v mém případě O2 (firmware V1.0.5_CZ.2T19). A spoiler rovnou na úvod: dá se z něj vytáhnout heslo admina úplně bez přihlášení.
Objev
Nejdřív mě zajímalo, co router vlastně dělá, takže jsem otevřel network tab v prohlížeči a sledoval requesty, které odcházejí ještě před přihlášením.
Hned mě zaujal request na endpoint /common_page/account_info_lua.lua s následující odpovědí:
<ajax_response_xml_root>
<IF_ERRORPARAM>SUCC</IF_ERRORPARAM>
<IF_ERRORTYPE>SUCC</IF_ERRORTYPE>
<IF_ERRORSTR>SUCC</IF_ERRORSTR>
<IF_ERRORID>0</IF_ERRORID>
<OBJ_USERINFO_ID>
<Instance>
<ParaName>_InstID</ParaName>
<ParaValue>IGD.AU1</ParaValue>
<ParaName>Password</ParaName>
<ParaValue>cmFuZG9tIHN0dWZmCg==</ParaValue>
<ParaName>Username</ParaName>
<ParaValue>admin</ParaValue>
</Instance>
</OBJ_USERINFO_ID>
<encode>Password</encode>
<OBJ_CZLOGIN_ID>
<Instance>
<ParaName>_InstID</ParaName>
<ParaValue>IGD</ParaValue>
<ParaName>RememberPwd</ParaName>
<ParaValue>0</ParaValue>
</Instance>
</OBJ_CZLOGIN_ID>
</ajax_response_xml_root>
V tu chvíli jsem byl lehce v šoku, koukal jsem na uživatelské jméno admina (admin) a jeho heslo, které na první pohled vypadalo jako base64. Ten <encode>Password</encode> na konci mi to ostatně napověděl - server v podstatě sám říká, které pole je zakódované. Zkusil jsem tedy otestovat svoji hypotézu pomocí base64 -d, ale vypadly z toho jen nečitelné znaky. Heslo je tedy nejspíš šifrované a base64 slouží pouze jako přenosový formát.
Podstatný detail: Endpoint sám o sobě žádnou autentizaci nevyžaduje. Cookie ale potřebujeme pro konzistenci session, protože server šifruje blob tokenem odvozeným z aktuální SID session (malý spoiler: ten token je přesně ta hodnota, kterou o kus dál najdeme volně v HTML login stránky jako klíč). Pokud pošleme oba requesty (načtení login stránky i volání endpointu) se stejnou cookie, dostaneme token i blob ze stejné session a dešifrování proběhne správně. Bez cookie nebo s rozdílnými session by token a klíč k blobu nesedly a dešifrování by selhalo.
Server tedy ochotně vydá zašifrované heslo admina komukoliv, kdo se umí zeptat. Už tohle je únik uživatelského jména a zašifrovaného hesla bez autentizace, což by samo o sobě na report bohatě stačilo. Mě ale zajímalo, jak daleko se s dopadem téhle chyby dá zajít.
Tenhle router má jednu zajímavou vlastnost: kryptografie pro funkci "Zapamatovat si mě" běží výhradně na klientovi. To znamená, že server musí klientovi dodat vše potřebné k dešifrování získaného hesla, včetně klíče. A přesně při hledání téhle logiky jsem narazil na funkci v client-side JavaScriptu.
Jak je heslo "chráněné"
O šifrování se stará tahle funkce:
function encodePara(data, key, iv) {
var key = CryptoJS.SHA256(key);
var iv = CryptoJS.SHA256(iv);
var encrypted = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
return encrypted.toString();
}
Žádná magie, obyčejné AES v režimu CBC se ZeroPadding. Vstupní key a iv se ještě proženou přes SHA-256 a výsledek slouží jako skutečný klíč a inicializační vektor. Zajímavé je, že stejným způsobem, kterým stránka data šifruje, je zároveň i dešifruje. Jinými slovy: jakmile budu mít klíč, celý proces si otočím.
A tím se dostáváme k jádru problému - kde je ten klíč?
Klíč přímo v HTML
Odpověď je tak trochu tragikomická. Klíč je v inline JavaScriptu přihlašovací stránky, kterou vám server naservíruje bez jakéhokoliv přihlášení:
var key = "307479559105475063077590";
var iv = key.split("").reverse().join("");
Takže:
- klíč je string přímo ve zdrojovém kódu stránky,
- IV se z něj odvozuje tím nejjednodušším možným způsobem - obrácením pořadí znaků.
Tahle hodnota je jediný "tajný" vstup celého mechanismu, který má chránit uložené heslo. Jenže tajná není ani náhodou - získá ji kdokoliv, kdo si zobrazí zdroj login stránky. Takže z pohledu ochrany jde spíš o dekoraci než o skutečné zabezpečení.
Abych byl fér, klíč se aspoň pravidelně rotuje, zhruba jednou za minutu. Přišel jsem na to vlastně náhodou, když se mi v jednu chvíli změnila zašifrovaná hodnota hesla. Nejdřív to vypadalo jako chyba, ale ve skutečnosti jsem se jen trefil do okamžiku, kdy se klíč přetočil. Na výsledku to ovšem nic nemění - v každém daném momentě je platný klíč veřejně dostupný na stránce, takže útočníkovi stačí prostě přečíst ten aktuální.
Dešifrování
Teď mám v ruce obě části skládačky:
- zašifrované heslo z
account_info_lua.lua(bez autentizace), - klíč ze zdrojového kódu login stránky (IV se z něj odvodí obrácením).
Zbytek je jen otočení funkce encodePara - vezme se klíč, zahashuje se SHA-256, IV se odvodí obrácením klíče a taktéž zahashuje, a base64 heslo se prožene AES-CBC dešifrováním se ZeroPadding. Dešifrování je plně možné v developer konzoli v prohlížeči, ale aby bylo testování komfortnější, napsal jsem si na to malý PoC skript, který ze zařízení automaticky vytáhne token (jehož hodnota je shodná s klíčem), stáhne zašifrované heslo a vrátí ho v čitelné podobě. Skript jsem na základě doporučení NÚKIB nezveřejnil ale výsledek byl jednoznačný:
------ RESULT ------
User: admin
Password: [...REDACTED...]
Heslo admina v plaintextu. Bez přihlášení. Čistě z věcí, které mi server sám dobrovolně vydá.
Root Cause Analysis
Hlavní důvod, proč tahle chyba vůbec existuje, je ten, že veškerá kryptografie pro funkci "Zapamatovat si mě" je delegovaná na klienta. To vytváří bezpečnostní dilema: klient musí buď znát heslo v plaintextu, nebo si ho musí umět nějakým způsobem odvodit, jako jsme viděli právě zde. Nejde ani tak o chybu v kódu - celá tahle funkce je insecure by design.
Dopad
Pojďme si shrnout, co chyba znamená v praxi. Útočníkovi stačí jediné - dosáhnout na webové rozhraní routeru. Žádné credentials, žádný brute-force, nic. Jakmile se k rozhraní dostane, získá plný administrátorský přístup, a s ním například možnost:
- měnit konfiguraci a vypínat bezpečnostní funkce,
- přesměrovat DNS a odposlouchávat nebo podvrhávat provoz (man-in-the-middle, phishing),
- vytáhnout nebo změnit Wi-Fi heslo (defaultně je k admin účtu stejné heslo jako k Wi-Fi),
- použít router jako vstupní bod do lokální sítě a útočit na další zařízení,
- zajistit si na zařízení trvalou přítomnost (persistence).
Výsledná závažnost pak hodně závisí na tom, jestli je admin rozhraní dostupné jen z lokální sítě, nebo i z internetu (WAN).
Přes 2 000 zařízení dostupných z internetu
A tady to začíná být nepříjemné. Přes Censys jsem si ověřil, kolik těchhle routerů má rozhraní vystavené přímo do internetu, a napočítal jsem přes 2 000 potvrzených zranitelných zařízení. Reálné číslo může být vyšší, pokud se stejná chyba objevuje i v jiných modelech ZTE routerů, které ISP nasazují. Chyba tím nabírá reálné rozměry a přestává být teoretickým problémem v laboratoři: ohrožuje skutečné uživatele, u kterých navíc útok proběhne klidně ze druhého konce světa, a to naprosto bez detekce - requesty pro získání zašifrovaného hesla a potřebného klíče se nijak neliší od běžného zobrazení přihlašovací stránky routeru.
Vyjádření ISP
O2 se k situaci vyjádřilo takto:
Dobrý den, pane Hrone,
děkujeme za podrobné upozornění a za zaslání reportu týkajícího se bezpečnostní zranitelnosti routeru ZTE ZXHN H267A.
Vaše hlášení jsme předali k interní provozní a bezpečnostní analýze. Zranitelnost následně detailně otestoval náš interní Red Team, který potvrdil její existenci i možnost jejího zneužití.
Zároveň jsme ověřovali podmínky, za kterých je možné zranitelnost skutečně využít. K úspěšnému zneužití je nutné, aby bylo změněno alespoň jedno z bezpečnostních nastavení, která jsou v základní konfiguraci zařízení nastavena tak, aby útoku bránila. Konkrétně jde o:
zpřístupnění administračního rozhraní routeru do internetu, které je ve výchozím nastavení zakázané, nebo
používání Wi-Fi bez odpovídajícího zabezpečení (ve výchozím nastavení je zapnuto WPA2).
Pokud zákazník žádnou z těchto změn neprovede, není podle našeho ověření možné popsanou zranitelnost tímto způsobem zneužít.
Současně jsme provedli kontrolu zařízení tohoto typu v naší zákaznické síti. Identifikovali jsme zákazníky, u kterých je administrační rozhraní routeru zpřístupněno do internetu. Tyto zákazníky budeme přímo kontaktovat s upozorněním na potřebu změny tohoto nastavení, případně jim doporučíme výměnu zařízení.
Router ZTE ZXHN H267A je již zastaralý model, který O2 v současnosti nenabízí a pro který již nejsou k dispozici nové verze firmwaru. Na tuto skutečnost zákazníky upozorňujeme také na našich webových stránkách, kde doporučujeme zařízení nahradit novějším typem.
Ještě jednou děkujeme za zodpovědné nahlášení a za poskytnutí podrobného technického reportu. Vašeho upozornění si vážíme a pomohlo nám provést cílenou kontrolu zařízení v naší síti a přijmout odpovídající opatření.
S pozdravem
O2.
Potom přišla ještě automatizovaná zpráva dobré vůle:
Mrzí nás, že služba Pevný internet ([REDACTED]) Vám nedávno nefungovala tak, jak by měla. Omlouváme se. Abychom to aspoň trochu napravili, v příštím vyúčtování najdete slevu 200 Kč. Částku jsme spočítali podle trvání poruchy ve dnech v poměru k ceně služby a najdete ji pod položkou „Jednorázová sleva za nefunkční služby".
Vaše O2
Což když už nic, tak se to dá počítat jako "bug bounty" :).
Zkušenost s responsible disclosure
Tohle byla moje první zkušenost s responsible disclosure (když nepočítám D-Link, který mi ani neodpověděl), a musím říct, že to nebyla úplně pozitivní zkušenost. O2 se mnou vůbec nekomunikovalo - vždy jsem musel volat nebo psát já - a nepodali mi žádné užitečné informace k chybě či plánované opravě. Deadline jsem sice nenastavoval, abych nedával zbytečný tlak na developery, protože vím, že v těchto situacích byrokracie dost zpomaluje celý proces. Ale i přesto jsem předpokládal, že po necelých 3 měsících budou mít alespoň plánované datum fixu. Nebo alespoň rychlou opravu, která dočasně odstraní funkci "Zapamatovat si mě", ale zabrání tak zneužití chyby. Proto jsem eskaloval na NÚKIB (Národní úřad pro kybernetickou a informační bezpečnost), kteří mě naštěstí dokázali zastoupit a pomocí nich dát větší nátlak na ISP, aby s tím něco aktivně dělal. Musím proto zde poděkovat NÚKIBu za řešení situace.
Doporučení
Pokud tenhle router doma máte:
- Zkontrolujte, že admin rozhraní není dostupné z internetu (WAN), a pokud ano, vypněte vzdálenou správu.
- Změňte heslo k admin účtu - a tím pádem i k Wi-Fi, pokud je defaultně stejné.
- Pokud jste touto chybou ovlivněni, O2 by vás mělo v nejbližší době kontaktovat.
Ideálně zvažte výměnu za novější model, protože pro tenhle router už neexistují aktualizace firmwaru.
Timeline
- 25. června 2026 - Objevena chyba
- 26. června 2026 - Sepsán a odeslán report ISP
- 8. července 2026 - Potvrzení chyby od ISP a odeslání chyby výrobci routeru (ZTE)
- 28. srpna 2026 - Eskalace na NÚKIB z důvodu slabé komunikace na straně ISP
- 8. října 2026 - Vyjádření od ISP
Závěr
Celý případ hezky ukazuje, že "zašifrované" ještě zdaleka neznamená "bezpečné". Když necháte dešifrovací klíč viset ve zdrojovém kódu veřejné stránky a heslo vydáváte bez přihlášení, je celá ta AES vrstva úplně k ničemu - v praxi jde jen o složitější base64. A z pohledu útočníka je to ideální scénář: dvě banální chyby, které se samy o sobě dají snadno přehlédnout, ale dohromady vedou ke kompletní kompromitaci účtu. Tenhle bug je taky těžko detekovatelný, protože informace, které útočník potřebuje k úspěšnému dešifrování hesla, jsou úplně stejné jako kdyby admin panel jen navštívil.


