Krótko: Zaciemniony kod PHP to kod celowo uczyniony nieczytelnym, zwykle przez kodowanie i kompresję, rozwiązywane dopiero w czasie działania. Typowe wzorce rozpoznawcze to eval razem z base64_decode, gzinflate lub str_rot13, bardzo długie wiersze, ciągi szesnastkowe oraz nazwy funkcji składane z części. Proszę szukać nieszkodliwymi poleceniami grep, nigdy nie wykonywać kodu i porównywać znaleziska z oryginalnym pakietem.
Dlaczego atakujący ukrywają swój kod
Kto umieszcza na serwerze webshell lub tylną furtkę, chce, aby długo pozostała niewykryta. Czytelny kod rzuciłby się w oczy każdemu administratorowi, który otworzy plik, a każdy skaner by go znalazł. Dlatego atakujący pakują swój kod: jest kodowany, kompresowany i dopiero przy wywołaniu składany na nowo i wykonywany.
Dla PHP2Shell nie ma potwierdzonych wskaźników kompromitacji (Indicators of Compromise). Zaciemniony kod nie jest cechą tej luki, lecz ogólnym znakiem, że ktoś wprowadził kod, niezależnie od drogi. Jak ogólnie znajdować i usuwać webshelle, opisuje Rozpoznawanie i usuwanie webshella. Ten artykuł koncentruje się na pytaniu, po czym poznać zaciemniony kod.
eval, base64 i inne: typowe elementy składowe
Zaciemnianie opiera się niemal zawsze na tej samej zasadzie: nieczytelny blok danych jest przez jedną lub kilka funkcji przekształcany z powrotem w kod, który następnie jest wykonywany. Poniższe elementy powtarzają się przy tym stale.
| Element | Przykłady | Dlaczego zwraca uwagę |
|---|---|---|
| Wykonanie | eval, assert z ciągiem znaków (do PHP 7.x), create_function (do PHP 7.x) |
wykonuje tekst jako kod |
| Dekodowanie | base64_decode, str_rot13, hex2bin, strrev, convert_uudecode |
czyni dane znów czytelnymi |
| Dekompresja | gzinflate, gzuncompress, gzdecode |
rozpakowuje osadzony kod |
| Składanie | łańcuchy chr(), łączenie krótkich fragmentów, sekwencje \x |
ukrywa nazwy funkcji |
| Wywołanie dynamiczne | zmienne jako nazwa funkcji, call_user_func, array_map z ciągiem znaków |
wywołanie nigdzie nie pojawia się jawnym tekstem |
| Dostęp do systemu | system, exec, shell_exec, passthru, proc_open, popen |
polecenia na serwerze |
Według podręcznika PHP assert() od PHP 8.0 nie traktuje już ciągów znaków jako kodu, a create_function() usunięto w PHP 8.0. Na serwerach ze starszymi wersjami PHP oba pozostają istotnymi hasłami wyszukiwania.
Pojedynczy element rzadko jest podejrzany, na przykład base64_decode wiele aplikacji używa zasadnie. Podejrzana staje się kombinacja: dekodowanie, którego wynik wpływa bezpośrednio do wykonania.
Bezpieczne wyszukiwanie: nieszkodliwe polecenia grep
Poniższe polecenia przeszukują pliki wyłącznie jako tekst. Nie wykonują żadnego kodu PHP. Ścieżkę proszę dostosować.
# Wykonanie połączone z dekodowaniem lub dekompresją
grep -rlE "(eval|assert)\s*\(\s*(base64_decode|gzinflate|gzuncompress|str_rot13|hex2bin)" /var/www --include="*.php*"
# Bardzo długie wiersze (często spakowany kod)
grep -rlE ".{2000,}" /var/www --include="*.php*"
# Wiele sekwencji szesnastkowych lub łańcuchów chr() w jednym wierszu
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*"
# Wywołanie funkcji przez zmienną bezpośrednio po przypisaniu z ciągów znaków
grep -rnE "\\\$[a-zA-Z_]+\s*=\s*['\"][a-z_]{4,}['\"]\s*;\s*\\\$[a-zA-Z_]+\s*\(" /var/www --include="*.php*" | head -50
Trafienia proszę najpierw sprawdzić przez ls -l i znacznik czasu, a potem otwierać wyłącznie jako tekst, na przykład przez less. Podejrzanych plików nigdy nie wolno wywoływać przez przeglądarkę, ponieważ serwer by je wykonał.
Gdzie zaciemniony kod zwykle siedzi
Zaciemniony kod rzadko stoi w osobnym, rzucającym się w oczy pliku. Częściej jest wstawiany do istniejących plików, na samym początku lub na samym końcu, często po wielu pustych wierszach, aby przy otwarciu nie rzucał się od razu w oczy. Popularne są pliki ładowane przy każdym wywołaniu: centralny index.php, pliki konfiguracyjne i pliki funkcji motywu.
Szczególnie niepozorna droga prowadzi przez konfigurację. Ustawieniem auto_prepend_file PHP ładuje przed każdym skryptem dodatkowy plik. Jeśli stoi w .user.ini lub .htaccess założonym przez atakującego, jego kod działa przy każdym wywołaniu strony, choć nie zmieniono ani jednego pliku aplikacji. Dołączany plik chętnie nosi nieszkodliwe rozszerzenie, takie jak .ico lub .png.
# Czy przed lub po każdym skrypcie ładowany jest dodatkowy kod?
grep -rnE "auto_(prepend|append)_file" /var/www --include=".user.ini" --include=".htaccess" --include="*.conf" 2>/dev/null
# Kod PHP w plikach z rozszerzeniem obrazu lub ikony
grep -rlE "<\?php" /var/www --include="*.ico" --include="*.png" --include="*.jpg" --include="*.gif" 2>/dev/null
Odróżnianie uzasadnionego zaciemniania od złośliwego kodu
Nie każdy nieczytelny kod jest złośliwy. Często zdarzają się trzy przypadki:
- Komercyjne enkodery: Niektórzy producenci wtyczek i oprogramowania chronią swój kod narzędziami takimi jak ionCube czy SourceGuardian. Pliki są nieczytelne, ale zwykle odsyłają do potrzebnego loadera. Proszę je porównać z oryginalnym pakietem.
- Kod zminimalizowany lub generowany: Pamięci podręczne i silniki szablonów tworzą długie wiersze bez złamań, jednak bez wykonywania zdekodowanego tekstu.
- Osadzone dane: Obrazy lub czcionki jako ciąg Base64 w pliku PHP są nietypowe, ale niegroźne, dopóki wynik nie jest wykonywany.
Najwiarygodniejszym testem jest porównanie ze źródłem: jeśli plik pochodzi niezmieniony z pakietu producenta, jest uzasadniony. Jeśli nie leży w żadnym oryginalnym pakiecie lub od niego odbiega, należy go zbadać.
Proszę nie próbować odszyfrowywać zaciemnionego kodu na serwerze produkcyjnym, na przykład zastępując eval wyjściem i wywołując plik. Już jeden błąd przy tym wykona złośliwy kod. Analiza należy do odizolowanego środowiska.
Granice wyszukiwania według wzorców
Atakujący znają popularne polecenia wyszukiwania. Dzielą nazwy funkcji, składają je w czasie działania albo wynoszą właściwy kod do bazy danych, plików obrazów lub wpisów konfiguracyjnych. Wyszukiwanie według wzorców znajduje więc dużo, ale nie wszystko. Dlaczego także skanery napotykają tu granice, wyjaśnia Skanery złośliwego oprogramowania dla stron: co znajdują, a czego nie.
Znalezisko rzadko jest ponadto jedyne. Atakujący używają w kilku plikach często tego samego elementu, na przykład rzucającej się w oczy nazwy zmiennej lub charakterystycznego ciągu znaków na początku zakodowanego bloku. Po potwierdzonym znalezisku proszę więc celowo szukać właśnie tego ciągu w całym katalogu WWW, także w innych stronach na tym samym serwerze. W ten sposób znajdą Państwo rodzeństwo, którego nie uchwyciło żadne z ogólnych wyszukiwań.
Wyszukiwanie proszę uzupełnić o porównanie z plikami oryginalnymi i spojrzenie do bazy danych, zob. Sprawdzanie bazy danych pod kątem wprowadzonego kodu. Prewencyjnie można zablokować niepotrzebne funkcje systemowe, jak opisano w poradniku Prawidłowe użycie disable_functions.
Jak konkretnie pomaga PHP SOS?
- Szukać: Przeszukujemy pliki i bazę danych rozszerzonymi wzorcami i porównujemy z oryginalnymi pakietami.
- Analizować: Badamy podejrzany kod w odizolowanym środowisku, nie narażając Państwa systemu.
- Oczyścić: W ramach usuwania złośliwego oprogramowania usuwamy wszystkie składniki i zamykamy drogę włamania.
- Zabezpieczyć: Ograniczamy funkcje PHP i uprawnienia zapisu oraz monitorujemy nowe lub zmienione pliki.
Częste pytania
Czy eval z base64_decode to zawsze złośliwy kod?
Nie zawsze, ale bardzo często. Uzasadniony kod prawie nie potrzebuje tej kombinacji. W plikach, które nie należą do Państwa aplikacji ani znanej wtyczki, jest to silny sygnał ostrzegawczy.
Czy mogę zdekodować zaciemniony kod, żeby go przeczytać?
Tylko bez jego wykonywania i nie na serwerze produkcyjnym. Już przypadkowe wykonanie może wyrządzić szkodę. W razie wątpliwości analizę proszę zostawić specjalistom, którzy pracują w odizolowanych środowiskach.
Dlaczego mój skaner złośliwego oprogramowania nie znajduje tego kodu?
Skanery szukają znanych wzorców. Atakujący stale zmieniają swój kod, dzielą nazwy funkcji lub składają je w czasie działania. Pusty wynik skanowania nie jest więc dowodem czystego serwera.
A co z komercyjnie szyfrowanymi wtyczkami?
Niektórzy producenci chronią swój kod enkoderami takimi jak ionCube czy SourceGuardian. Takie pliki są nieczytelne, ale uzasadnione, jeśli pochodzą od producenta. Proszę je porównać z oryginalnym pakietem.
Czy disable_functions pomaga przeciw zaciemnionemu kodowi?
Częściowo. Funkcje takie jak exec czy system można zablokować, eval natomiast nie, ponieważ jest konstrukcją języka, a nie funkcją. Blokada utrudnia atakującym następny krok, ale nie zapobiega każdemu złośliwemu kodowi.