Zastanawialiście się kiedyś, co może pójść nie tak, gdy połączymy wtyczkę do kulinarnych przepisów z jedną z najniebezpieczniejszych i najbardziej przestarzałych funkcji w PHP? Otóż można ugotować niezły bałagan.
Podczas moich badań nad bezpieczeństwem popularnych rozszerzeń do WordPressa natrafiłem na klasyczny błąd typu PHP Object Injection (Insecure Deserialization) w pluginie Cooked Pro. Moje zgłoszenie zaowocowało przypisaniem oficjalnego numeru CVE-2022-3900 (zostało m.in. skatalogowane przez WPScan oraz bazę Wiz.io). Luka otrzymała na platformie NVD bezwzględny scoring 9.8 (Critical), choć niektóre bazy oceniają ją na mocne 7.4 (High).
Zobaczmy, dlaczego ślepe ufanie danym od użytkownika i przepuszczanie ich przez funkcję unserialize() to idealny przepis na oddanie serwera w ręce hakera. I to bez konieczności logowania!
TL;DR - Najważniejsze wnioski
- Co poszło nie tak? (Endpointy i założenia)
- Proof of Concept (Weryfikacja Koncepcji)
- Jak ustrzec się przed takimi wpadkami? (Remediacja)
Co poszło nie tak? (Endpointy i założenia)
Wtyczka Cooked Pro (podatna w wersjach starszych niż 1.7.5.7) oferuje bardzo wygodną funkcjonalność, pozwala na doładowywanie kolejnych przepisów na stronie w sposób asynchroniczny (często realizowane przyciskiem "Load More").
Żądanie z frontendu leci prosto do standardowego w ekosystemie WordPressa pliku obsługującego AJAX: /wp-admin/admin-ajax.php, gdzie docelowo wywoływana jest akcja:
Wśród przesyłanych parametrów znalazł się jeden bardzo ciekawy element: recipe_args. Deweloperzy wtyczki wpadli na dość niefortunny pomysł, aby argumenty dla przepisu przesyłać ze strony klienta w formie zserializowanego (zapakowanego) obiektu PHP.
Backend pobierał parametr recipe_args prosto z zapytania i od razu wrzucał go do natywnej funkcji unserialize(). Brak autoryzacji? Brak sprawdzania i czyszczenia (sanitization) danych wejściowych? Mamy to. Błąd skatalogowany jako CWE-502 (Deserialization of Untrusted Data) stał otworem.
Dlaczego unserialize() jest niebezpieczne?
Funkcja unserialize() w PHP odtwarza pełne obiekty PHP z ciągu znaków, włącznie z ich klasą i właściwościami. Jeśli atakujący kontroluje dane wejściowe, może stworzyć dowolny obiekt z klasy dostępnej w pamięci aplikacji i wywołać jego metody magiczne (__wakeup, __destruct), co prowadzi do wykonania dowolnego kodu.
Proof of Concept (Weryfikacja Koncepcji)
Aby wykorzystać ten błąd, nie potrzebowaliśmy żadnego konta w systemie WordPress. Atakujący musiał po prostu wysłać do serwera odpowiednio spreparowane żądanie POST:
Magia dzieje się w miejscu, które nazwałem <SERIALIZED_PHP_OBJECT>. Kiedy PHP deserializuje obiekty, automatycznie próbuje wywołać w nich tzw. "magiczne metody" (np. __wakeup() czy __destruct()).
Jeśli atakujący podstawi pod recipe_args złośliwy ładunek (tzw. POP chain. Property Oriented Programming), zawierający klasy dostępne w środowisku WordPressa (lub z innych aktywnych wtyczek/motywów), może zmusić serwer do wykonania groźnych operacji:
- Usunięcie dowolnego pliku na serwerze (np. wp-config.php)
- Odczytanie bazy danych i kradzież danych użytkowników
- Zresetowanie hasła administratora
- Docelowo: pełne Remote Code Execution (RCE)
recipe_args=O:8:"stdClass":...unserialize($recipe_args)Zaleznie od dostepnych klas w srodowisku WordPress, atakujacy moze:
Jak ustrzec się przed takimi wpadkami? (Remediacja)
Błędy typu Insecure Deserialization od dawna plasują się wysoko w zestawieniach OWASP Top 10, a w starych aplikacjach i wtyczkach PHP są wręcz plagą. Przepis na bezpieczny kod składa się zaledwie z dwóch składników:
- Nigdy nie ufaj unserialize(): Złota i nienaruszalna zasada programowania w PHP. Funkcja unserialize() nigdy nie powinna przyjmować danych pochodzących od użytkownika, z ciasteczek lub z parametrów GET/POST.
- Przejdź na JSON: Jeśli musisz przesyłać złożone struktury (takie jak wielowymiarowe tablice czy argumenty, jak we wspomnianym recipe_args) pomiędzy klientem a serwerem, zawsze używaj formatu JSON. Zastosowanie natywnych i bezpiecznych funkcji json_encode() oraz json_decode() całkowicie eliminuje to ryzyko.
Odtwarza pelne obiekty PHP z ciagu znakow, wlacznie z klasami i metodami magicznymi.
Przenosi jedynie wartosci i strukture danych. Ignoruje obiekty i klasy wykonywalne PHP.
Podsumowanie
CVE-2022-3900 to klasyczny przykład nieuwierzytelnionego PHP Object Injection przez niebezpieczną funkcję unserialize(). Wtyczka Cooked Pro dla WordPressa pozwalała każdemu użytkownikowi internetu na wstrzyknięcie złośliwych obiektów PHP poprzez parametr recipe_args w zapytaniu AJAX. W najgorszym scenariuszu atakujący mógł uzyskać pełny Remote Code Execution na serwerze. Rozwiązanie jest proste, zamiana unserialize() na json_decode() i walidacja danych wejściowych.
Bibliografia
Oficjalny wpis w bazie NIST NVD opisujący podatność PHP Object Injection w pluginie Cooked Pro z oceną CVSS 9.8 (Critical).
Oficjalny wpis MITRE opisujący klasę podatności polegającą na deserializacji niezaufanych danych, dokładnie ten typ błędu, który został wykorzystany w CVE-2022-3900.
Wpis WPScan dokumentujący podatność PHP Object Injection w pluginie Cooked Pro, włącznie ze szczegółami technicznymi i wersjami dotkniętymi błędem.
Przewodnik OWASP dotyczący testowania i zapobiegania atakom typu Insecure Deserialization, jednej z kategorii OWASP Top 10.
Oficjalna dokumentacja PHP dla funkcji unserialize() z ostrzeżeniem bezpieczeństwa: "Do not pass untrusted user input to unserialize()".

