Stručně: Zašifrovaný (obfuskovaný) kód PHP je kód, který byl záměrně učiněn nečitelným, většinou kódováním a kompresí, které se rozbalí až za běhu. Typickými rozpoznávacími vzory jsou eval společně s base64_decode, gzinflate nebo str_rot13, velmi dlouhé řádky, hexadecimální řetězce a názvy funkcí skládané z dílčích částí. Hledejte neškodnými příkazy grep, kód nikdy nespouštějte a nálezy porovnávejte s originálním balíčkem.
Proč útočníci svůj kód ukrývají
Kdo ukládá na server webshell nebo zadní vrátka, chce, aby zůstaly dlouho neobjeveny. Čitelný kód by upoutal každého administrátora, který soubor otevře, a každý skener by jej našel. Útočníci proto svůj kód balí: kóduje se, komprimuje a teprve při zavolání se znovu složí a spustí.
Pro PHP2Shell neexistují potvrzené indikátory kompromitace (Indicators of Compromise). Zašifrovaný kód není znakem této zranitelnosti, ale obecným znamením, že někdo vložil kód, ať už jakoukoli cestou. Jak webshelly celkově najít a odstranit, popisuje Rozpoznání a odstranění webshellu. Tento článek se soustředí na otázku, podle čeho zašifrovaný kód poznáte.
eval, base64 a spol.: typické stavební kameny
Zašifrování se téměř vždy řídí stejným principem: Nečitelný blok dat se jednou nebo několika funkcemi převede zpět na kód, který se pak spustí. Následující stavební kameny se tu opakují.
| Stavební kámen | Příklady | Proč je nápadný |
|---|---|---|
| Spuštění | eval, assert s řetězcem (do PHP 7.x), create_function (do PHP 7.x) |
spouští text jako kód |
| Dekódování | base64_decode, str_rot13, hex2bin, strrev, convert_uudecode |
činí data opět čitelnými |
| Dekomprese | gzinflate, gzuncompress, gzdecode |
rozbaluje vložený kód |
| Skládání | řetězce chr(), spojování krátkých částí, escapy \x |
skrývá názvy funkcí |
| Dynamické volání | proměnné jako název funkce, call_user_func, array_map s řetězcem |
volání se nikde neobjeví v prostém textu |
| Přístup k systému | system, exec, shell_exec, passthru, proc_open, popen |
příkazy na serveru |
Podle příručky PHP assert() od PHP 8.0 už nevyhodnocuje řetězce jako kód a create_function() byla v PHP 8.0 odstraněna. Na serverech se starými verzemi PHP zůstávají oba pojmy relevantními hledanými výrazy.
Jediný stavební kámen je zřídka podezřelý, base64_decode například legitimně používá mnoho aplikací. Podezřelou se stává kombinace: dekódování, jehož výsledek přímo vstupuje do spuštění.
Bezpečně hledat: neškodné příkazy grep
Následující příkazy prohledávají soubory jen jako text. Nespouštějí žádný kód PHP. Cestu upravte.
# Spuštění kombinované s dekódováním nebo dekompresí
grep -rlE "(eval|assert)\s*\(\s*(base64_decode|gzinflate|gzuncompress|str_rot13|hex2bin)" /var/www --include="*.php*"
# Velmi dlouhé řádky (často zabalený kód)
grep -rlE ".{2000,}" /var/www --include="*.php*"
# Mnoho escapů hex nebo řetězců chr() v jednom řádku
grep -rlE "(\\\\x[0-9a-fA-F]{2}){20,}" /var/www --include="*.php*"
grep -rlE "(chr\s*\(\s*[0-9]+\s*\)\s*\.\s*){8,}" /var/www --include="*.php*"
# Volání funkce přes proměnnou přímo po přiřazení z řetězců
grep -rnE "\\\$[a-zA-Z_]+\s*=\s*['\"][a-z_]{4,}['\"]\s*;\s*\\\$[a-zA-Z_]+\s*\(" /var/www --include="*.php*" | head -50
Nálezy nejprve zkontrolujte pomocí ls -l a časového razítka a pak je otevřete jen jako text, například pomocí less. Podezřelé soubory nikdy nevolejte přes prohlížeč, protože by je server spustil.
Kde zašifrovaný kód typicky sedí
Zašifrovaný kód zřídka stojí ve vlastním nápadném souboru. Častěji se vkládá do existujících souborů, úplně na začátek nebo úplně na konec, často po mnoha prázdných řádcích, aby při otevření hned neupoutal pozornost. Oblíbené jsou soubory, které se načítají při každém volání: centrální index.php, konfigurační soubory a soubory funkcí šablony.
Zvlášť nenápadná cesta vede přes konfiguraci. Nastavením auto_prepend_file načte PHP před každým skriptem další soubor. Je-li uvedeno v .user.ini nebo .htaccess, které útočník založil, běží jeho kód při každém zobrazení stránky, aniž by se změnil jediný soubor aplikace. Vložený soubor přitom rád nese neškodnou příponu jako .ico nebo .png.
# Načítá se před nebo po každém skriptu nějaký další kód?
grep -rnE "auto_(prepend|append)_file" /var/www --include=".user.ini" --include=".htaccess" --include="*.conf" 2>/dev/null
# Kód PHP v souborech s příponou obrázku nebo ikony
grep -rlE "<\?php" /var/www --include="*.ico" --include="*.png" --include="*.jpg" --include="*.gif" 2>/dev/null
Legitimní zašifrování odlišit od škodlivého kódu
Ne každý nečitelný kód je zlovolný. Často se vyskytují tři případy:
- Komerční enkodéry: Někteří výrobci pluginů a softwaru chrání svůj kód nástroji jako ionCube nebo SourceGuardian. Soubory jsou nečitelné, ale typicky odkazují na potřebný loader. Porovnejte je s originálním balíčkem.
- Minifikovaný nebo generovaný kód: Cache a šablonovací enginy vytvářejí dlouhé řádky bez zalomení, avšak bez spouštění dekódovaného textu.
- Vložená data: Obrázky nebo písma jako řetězec Base64 v souboru PHP jsou neobvyklé, ale neškodné, dokud se výsledek nespouští.
Nejspolehlivějším testem je porovnání se zdrojem: Pochází-li soubor beze změny z balíčku výrobce, je legitimní. Neleží-li v žádném originálním balíčku nebo se od něj liší, patří k prošetření.
Nepokoušejte se zašifrovaný kód dešifrovat na produkčním serveru, například tak, že eval nahradíte výpisem a soubor zavoláte. Už jediná chyba přitom spustí škodlivý kód. Analýza patří do izolovaného prostředí.
Hranice hledání podle vzorů
Útočníci znají běžné příkazy pro hledání. Dělí názvy funkcí, skládají je za běhu nebo vlastní kód vyčleňují do databáze, do obrazových souborů či do konfiguračních záznamů. Hledání podle vzorů proto najde mnoho, ale ne vše. Proč i skenery narážejí na hranice, vysvětluje Skenery malwaru pro weby: co najdou a co ne.
Nález navíc zřídka bývá jediný. Útočníci často používají ve více souborech stejný stavební kámen, například nápadné označení proměnné nebo výrazný řetězec znaků na začátku kódovaného bloku. Po potvrzeném nálezu proto hledejte cíleně právě tento řetězec v celém webovém adresáři, i v dalších webech na tomtéž serveru. Tak najdete sourozence, které žádné obecné hledání podle vzorů nezachytilo.
Hledání doplňte porovnáním s originálními soubory a pohledem do databáze, viz Kontrola databáze na vložený kód. Preventivně lze zakázat nepotřebné systémové funkce, jak popisuje průvodce Správné použití disable_functions.
Jak konkrétně pomáhá PHP SOS?
- Hledat: Prohledáváme soubory a databázi pomocí rozšířených vzorů a porovnáváme s originálními balíčky.
- Analyzovat: Podezřelý kód zkoumáme v izolovaném prostředí, aniž bychom ohrozili váš systém.
- Vyčistit: S odstraněním malwaru odstraníme všechny součásti a uzavřeme cestu průniku.
- Zabezpečit: Omezujeme funkce PHP a oprávnění k zápisu a sledujeme nové nebo změněné soubory.
Časté dotazy
Je eval s base64_decode vždy škodlivý kód?
Ne vždy, ale velmi často. Legitimní kód tuto kombinaci sotva potřebuje. V souborech, které nepatří k vaší aplikaci ani ke známému pluginu, je to silný varovný signál.
Smím zašifrovaný kód dekódovat, abych ho přečetl?
Jen bez jeho spuštění a ne na produkčním serveru. Už nechtěné spuštění může způsobit škodu. V případě pochybností nechte analýzu odborníkům, kteří pracují s izolovanými prostředími.
Proč můj skener malwaru kód nenajde?
Skenery hledají známé vzory. Útočníci svůj kód neustále mění, dělí názvy funkcí nebo je skládají za běhu. Prázdný sken proto není důkazem čistého serveru.
Co komerčně zašifrované pluginy?
Někteří výrobci chrání svůj kód enkodéry jako ionCube nebo SourceGuardian. Takové soubory jsou nečitelné, ale legitimní, pokud pocházejí od výrobce. Porovnejte je s originálním balíčkem.
Pomáhá disable_functions proti zašifrovanému kódu?
Částečně. Funkce jako exec nebo system lze zakázat, eval naopak ne, protože je jazykovým konstruktem, ne funkcí. Zákaz útočníkům ztěžuje další krok, ale nezabrání každému škodlivému kódu.