Nedávno objevená zranitelnost ve WordPress 6.4 poukázala na útok pomocí PHP Object Injection (POI). Jedná se o jednu z méně známých, ale vysoce závažných bezpečnostních hrozeb v PHP aplikacích. Tento druh útoku zůstává ve stínu běžnějších hrozeb jako SQL injection nebo Cross-Site Scripting (XSS), přitom má potenciál způsobit vážné škody.
Jak k útoku dochází
Tento druh útoku vyžaduje velmi dobrou znalost cílové aplikace a všech rozšíření. Základem je znát část kódu, kde aplikace deserializuje vstupní data. Útočník se pokusí vytvořit nebo upravit serializovaný řetězec tak, aby obsahoval objekty se škodlivým účinkem.
Když je tento řetězec deserializován, může dojít k automatickému spuštění metod nebo změně vlastností, což může vést k neautorizovaným akcím, jako je přístup k databázi, změna souborů nebo dokonce spuštění škodlivého kódu.
Serializace je proces převodu datové struktury nebo objektu do formátu, který lze uchovat nebo přenášet. Deserializace je opačný proces, který převádí data zpět na původní datovou strukturu nebo objekt.
Příklad serializace
Mějme třídu User a vytvořme instanci této třídy:
class User {
public $name;
public $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
}
// Vytvoření instance User
$user = new User("Alice", 30);
Nyní serializujeme objekt $user:
$serializedUser = serialize($user);
echo $serializedUser;
Tento kód vypíše serializovaný řetězec, který reprezentuje objekt $user. Výstup by mohl vypadat takto:
O:4:"User":2:{s:4:"name";s:5:"Alice";s:3:"age";i:30;}
Příklad deserializace
Pro deserializaci tohoto řetězce zpět na objekt User použijeme funkci unserialize:
$unserializedUser = unserialize($serializedUser);
// Výpis vlastností objektu
echo $unserializedUser->name; // Alice
echo $unserializedUser->age; // 30
Jednoduchý příklad útoku POI
Ukážeme si jednoduchý příklad, fiktivního kódu a jak proběhne útok typu POI.
Předpokládejme, že máme následující třídu Logger a skript, který deserializuje data z vstupu a očekává instanci Logger.
class Logger {
public $logFile;
function log($message) {
file_put_contents($this->logFile, $message . "\n", FILE_APPEND);
}
}
Příklad zranitelnosti
Předpokládejme, že naše aplikace neopatrně deserializuje data z neověřeného vstupu, například z GET nebo POST požadavku. Toto je velká zranitelnost.
// Nebezpečná deserializace
$data = $_GET['data'];
$logger = unserialize($data);
// Kontrola, zda je objekt Logger
if ($logger instanceof Logger) {
// Zapisuje do logu
$logger->log("Log záznam");
}
Provedení útoku
Útočník nyní může vytvořit serializovaný objekt, který zneužije tuto zranitelnost.
- Přijetí škodlivého kódu: Skript obdrží serializovaný objekt v parametru
dataz GET požadavku. - Deserializace a aktivace: Skript deserializuje objekt a volá na něm metodu
log. - Následky útoku: Volání metody
logzpůsobí, že zadaný text bude zapsán do cílového souboru (/path/to/config.php). Tím může útočník změnit konfiguraci aplikace, zapsat škodlivý kód nebo provést jiné nežádoucí akce.
Kód s útokem je serializovaný řetězec:
O:6:"Logger":1:{s:7:"logFile";s:18:"/path/to/config.php";}
Projdeme si to krok za krokem
Přijetí škodlivého kódu:
Skript obdrží škodlivý kód jako součást vstupu z uživatele, obvykle prostřednictvím HTTP GET nebo POST požadavku. V tomto příkladu je útočný kód předán jako hodnota parametru v URL, například:
http://example.tld/script.php?data=O:6:"Logger":1:{s:7:"logFile";s:18:"/path/to/config.php";}.
Deserializace škodlivého kódu:
Skript používá funkci unserialize() k deserializaci dat přijatých v proměnné $_GET['data']. Tato funkce převede serializovaný řetězec zpět na objekt PHP. V našem příkladu unserialize() vytvoří instanci třídy Logger s vlastností logFile nastavenou na /path/to/config.php.
Konkrétně, co přesně se skrývá za školdivým kódem:
O:6:"Logger":1:{s:7:"logFile";s:18:"/path/to/config.php";}
O:6:"Logger":1:: Indikuje, že následující data reprezentují objekt třídyLoggers jednou vlastností.Oznamená objekt.6je délka názvu třídy, tedy „Logger“.1je počet vlastností, které tento objekt má.
{s:7:"logFile";s:18:"/path/to/config.php";}: Popisuje vlastnosti objektu.sznamená, že následující data jsou řetězec (string).7je délka názvu vlastnosti „logFile“.18je délka hodnoty/path/to/config.php.
Když je tento kód deserializován pomocí unserialize(), PHP vytvoří následující:
- Instance třídy
Logger. - Tato instance má jednu vlastnost:
- logFile nastavenou na hodnotu „/path/to/config.php“.
Výsledkem deserializace by byl objekt Database vypadající přibližně takto:
object(Logger)#1 (1) {
["logFile"]=>
string(18) "/path/to/config.php"
}
Aktivace škodlivého kódu:
Pokud skript po deserializaci volá metodu log na deserializovaném objektu, dojde k zapsání textu do souboru určeného vlastností logFile. V našem případě by metoda log zapsala záznam do souboru /path/to/config.php
Realizace útoku:
V tomto bodě se útočníkův záměr realizuje. Pokud skript skutečně volá metodu log na deserializovaném objektu, může to mít za následek přepsání nebo modifikaci důležitého souboru, což může vést k nežádoucím důsledkům.
Důsledky útoku:
Výsledkem je, že útočník dosáhl svého cíle prostřednictvím deserializovaného objektu. V závislosti na tom, co bylo v útočném kódu určeno, mohou být důsledky útoku různé – například od modifikace konfiguračního souboru, přes narušení funkčnosti aplikace, až po plnou kontrolu nad aplikací nebo serverem.
Závěr
V praxi je tedy pro realizaci útoku POP potřeba, aby aplikace obsahovala určitou logiku, která po deserializaci objektu vyvolává volání metod, a tyto metody by pak mohly být zneužity k provádění nechtěných akcí. To znamená, že zranitelnost vzniká kombinací nebezpečné deserializace a dalších aspektů aplikace, které umožňují zneužití deserializovaných objektů.
V samotném WordPress jak se ukázalo přímo zranitelnost nemusí být proveditelná, ale teprve v některém z nespočtu rozšíření už ano.