CleanOps

Malware w bazie danych — czego skaner plików nigdy nie znajdzie

12 października 2026 • 3 min czytania • baza danych, WordPress, Joomla, SQL, malware

Biurko z laptopem, książkami i otwartym notatnikiem

Scenariusz, który wraca regularnie: ktoś wyczyścił pliki, porównał rdzeń z wydaniem, usunął web‑shelle, a strona dalej wyświetla obce reklamy albo przekierowuje z telefonu. Pliki są czyste, bo infekcja nie jest w plikach. Jest w bazie danych, a większość skanerów bazy w ogóle nie czyta.

Dlaczego atakujący wybierają bazę

Trzy powody. Po pierwsze, skanery plików (także te hostingowe) tego nie widzą, więc infekcja żyje dłużej. Po drugie, treść z bazy jest wyświetlana na każdej podstronie bez potrzeby modyfikowania kodu. Po trzecie, nawet pełna reinstalacja CMS‑a z czystych plików nie pomaga, bo baza zostaje ta sama.

Gdzie szukać w WordPressie

wp_options

To najważniejsze miejsce. Sprawdź:

  • siteurl i home — zmienione na obcą domenę oznaczają przekierowanie całej strony;
  • active_plugins — wtyczki, których nie ma w panelu, a są aktywne;
  • opcje widżetów (widget_text, widget_custom_html) — doklejone <script> lub <iframe>;
  • opcje motywu i wtyczek typu „wstaw kod w nagłówku” — tam atakujący wkleja skrypt, bo wtyczka legalnie go wyświetli na każdej stronie;
  • dowolne opcje o losowych nazwach z długą wartością zaczynającą się od a: lub eval.
SELECT option_name, LEFT(option_value, 120)
FROM wp_options
WHERE option_value LIKE '%<script%'
   OR option_value LIKE '%eval(%'
   OR option_value LIKE '%base64_%'
   OR option_value LIKE '%fromCharCode%';

wp_posts

Skrypt doklejony do treści każdego wpisu, strony albo produktu:

SELECT ID, post_title, post_type
FROM wp_posts
WHERE post_content LIKE '%<script%'
   OR post_content LIKE '%<iframe%'
   OR post_content LIKE '%display:none%';

Część trafień będzie uzasadniona (osadzone wideo, własne skrypty). Zwracaj uwagę na te same fragmenty powtórzone w setkach wpisów i na domeny, których nie znasz.

wp_users i wp_usermeta

SELECT ID, user_login, user_email, user_registered FROM wp_users ORDER BY user_registered DESC LIMIT 20;

Konta administratorów założone w nocy, z adresami na losowych domenach. Sprawdź też wp_usermeta dla klucza wp_capabilities — użytkownik może mieć login „subscriber”, a uprawnienia administratora.

Zaplanowane zadania

W wp_options klucz cron. Zadania o nieznanych nazwach, uruchamiane co godzinę, to częsty sposób na ponowne pobieranie infekcji po jej usunięciu.

Gdzie szukać w Joomli

Prefiks tabel jest losowy (np. abc12_), znajdziesz go w configuration.php jako $dbprefix.

  • _modules — moduł typu mod_custom (Custom HTML) z <script> w kolumnie content, przypisany do wszystkich stron, często w pozycji szablonu, która nie jest widoczna;
  • _extensions — kolumna params i custom_data z zaciemnionym kodem; rozszerzenia włączone, których nie instalowałeś;
  • _template_styles — parametry szablonu, w tym pola na własny kod;
  • _content — kolumna introtext i fulltext z doklejonym skryptem;
  • _users i _user_usergroup_map — obce konta w grupie Super Users (group_id = 8 w domyślnej instalacji);
  • _session — aktywne sesje obcych użytkowników.
SELECT id, title, position, LEFT(content, 120)
FROM abc12_modules
WHERE content LIKE '%<script%' OR content LIKE '%<iframe%';

Jak usunąć bez kasowania treści

Nie kasuj rekordów. Doklejony skrypt jest fragmentem wartości, a reszta wartości to Twoja treść. Zrób kopię bazy, a potem:

  1. Dla pojedynczych rekordów (opcje, moduły) popraw wartość ręcznie w phpMyAdmin albo w panelu CMS‑a.
  2. Dla setek wpisów z identycznym fragmentem użyj REPLACE, ale najpierw sprawdź dokładną treść fragmentu, bo spacja różnicy sprawi, że część zostanie:
UPDATE wp_posts
SET post_content = REPLACE(post_content, '<script src="https://obca-domena/x.js"></script>', '')
WHERE post_content LIKE '%obca-domena/x.js%';
  1. Uwaga na wartości serializowane (a:3:{s:5:"title";...}) w wp_options i _extensions.params. Zawierają długości łańcuchów. Jeśli usuniesz fragment ręcznie, długość przestanie się zgadzać i CMS odrzuci całą wartość. Takie rekordy poprawiaj z poziomu panelu CMS‑a (który zapisze je poprawnie) albo narzędziem, które rozumie serializację.
  2. Usuń obce konta i zmień hasła pozostałych administratorów.
  3. Wyczyść cache CMS‑a i wtyczek, bo stara treść ze skryptem może być zapisana w cache.

Jak sprawdzić, czy to koniec

Pobierz stronę jako telefon z Google i jako Googlebot (komendy w naszych wpisach o przekierowaniach i spamie SEO). Jeśli odpowiedź jest czysta, powtórz zapytania SQL po tygodniu. Jeśli fragment wrócił, w plikach zostało coś, co go dopisuje, zwykle zaplanowane zadanie albo wtyczka w mu-plugins.

Skan CleanOps obejmuje pliki, nie bazę. Bazę sprawdzisz po naprawie z asystentem AI w panelu: ma to samo połączenie z serwerem co skan, dane do bazy odczyta z pliku konfiguracyjnego, a każde zapytanie, które coś zmienia, wykona dopiero po Twoim zatwierdzeniu. Zapytania z tego wpisu możesz mu wkleić wprost.

Czytaj też