Skip to content
EN
← Все статьи

Как проверить сайт на вирусы: 4 уровня проверки и план лечения

Коротко. Проверить сайт на вирусы одним сканером нельзя: внешние сервисы видят только то, что сервер отдал по HTTP. Нужны четыре независимых уровня — репутационные базы и сканеры страниц снаружи, DevTools в браузере, файловая система на сервере и база данных. Заражение подтверждают свежие по mtime файлы, обфускация в PHP и разный ответ сайта разным User-Agent.

Как понять, что сайт заражён: симптомы глазами владельца

Владелец почти никогда не видит вредоносный код напрямую. Он видит косвенные признаки, и почти каждый из них выглядит как «что-то сломалось», а не как «нас взломали». Именно поэтому между заражением и обнаружением обычно проходят недели: заражённый сайт продолжает работать, открываться и принимать заказы.

Типичный набор симптомов, с которого начинается расследование:

  • Редиректы, которые вы не воспроизводите. Посетители жалуются, что их уводит на посторонний сайт, а у вас всё открывается нормально. Почти всегда это условный редирект: только с мобильных, только при переходе из поиска, только для тех, кто зашёл впервые.
  • Всплеск 404 в логах и панели вебмастера. Сотни или тысячи несуществующих адресов, которых вы не создавали. Это следы дорвеев, которые уже удалили или переименовали, но поисковик их успел проиндексировать.
  • Чужие страницы в индексе. Поиск по оператору site: показывает страницы про аптеку, азартные игры, реплики или займы. Ваш сайт стал площадкой для чужого SEO.
  • Письмо от хостинг-провайдера. «С вашего аккаунта идёт спам», «обнаружен вредоносный файл», «аккаунт приостановлен». Хостеры сканируют файлы клиентов и часто узнают о проблеме раньше владельца.
  • Падение позиций без изменений на сайте. Трафик просел, хотя ничего не меняли. В Search Console или Яндекс.Вебмастере появляется раздел с предупреждением о безопасности.
  • Предупреждение браузера. Красный экран «Обманчивый сайт» или «Впереди сайт с вредоносным ПО». Это уже публичная стадия: сайт попал в базу Google Safe Browsing.
  • Скачки нагрузки. Хостинг ругается на превышение лимитов CPU, растёт исходящий трафик, но посещаемость прежняя. Обычно это майнер, рассыльщик спама или прокси-скрипт.
  • Новые пользователи в админке. Учётные записи с правами администратора, которые вы не создавали, или изменённая почта у существующего администратора.

Почему антивирус на компьютере тут бесполезен

Частая ошибка — прогнать «сайт на вирусы» антивирусом на своём ноутбуке. Это не работает по трём причинам, и понимать их важно, чтобы не терять время.

Во-первых, вредоносный код живёт на сервере, а не у вас. Антивирус проверяет файлы локального диска; PHP-бэкдор в каталоге сайта на хостинге в поле его зрения не попадает в принципе. Во-вторых, серверный код исполняется до того, как браузер получит ответ: PHP-шелл никогда не приезжает к вам в виде файла, вы получаете только результат его работы — HTML. В-третьих, антивирус ориентирован на исполняемые файлы под вашу ОС, а не на PHP, JavaScript и SQL-инъекции в контенте CMS.

Единственное, что локальный антивирус реально ловит, — попытку скачать заражённый файл с уже взломанного сайта. То есть он защищает вас как посетителя, а не ваш сайт как ресурс.

Правило: если вы владелец сайта, проверять надо не устройство, с которого вы смотрите сайт, а сервер, на котором сайт лежит. Всё остальное — проверка вторичных следов.

Схема: симптомы заражения сайта — редиректы, всплеск 404, чужие страницы в индексе, предупреждение браузера и рост нагрузки
Владелец видит не вирус, а его следствия: редиректы, дорвеи в индексе, письма хостера и скачки нагрузки

Таблица ниже связывает симптом с местом проверки — с неё удобно начинать, если признак уже есть.

СимптомГде проверятьЧемЧто это обычно значит
Редирект только с мобильныхОтвет сервера с мобильным User-Agentcurl -A с мобильным UA, режим устройства в DevToolsУсловие по User-Agent в .htaccess, index.php или во внедрённом JS
Редирект только при переходе из поискаОтвет при наличии заголовка Referercurl -e "https://www.google.com/"Клоакинг по Referer; чаще всего вставка в шапке шаблона или в mu-plugins
Всплеск 404Логи веб-сервера, отчёты вебмастераgrep по access-логу, отчёт по страницамДорвеи уже удалены или переименованы, но остались в индексе поисковика
Чужие страницы в индексеВыдача по оператору site:Поиск в Google и ЯндексеРаботает генератор дорвеев — ищите его в файлах и в базе данных
Письмо хостера про спамОчередь и логи почтовой подсистемыmailq, логи MTA, статистика панелиНа сайте скрипт рассылки; почти всегда рядом есть бэкдор
Падение позиций без правокРаздел безопасности в панелях вебмастераSearch Console, Яндекс.ВебмастерСанкции за вредоносный код, фишинг или SEO-спам
Предупреждение браузераРепутационные базыПроверка на вредоносный код, Safe BrowsingДомен или подгружаемый им ресурс попал в базу
Рост CPU и исходящего трафикаМетрики хостинга и процессы сервераtop, статистика панели, access-логМайнер, рассыльщик, открытый прокси или участие в атаке
Новые администраторы в CMSТаблица пользователей CMSАдминка, запрос к базе данныхКомпрометация подтверждена: смены пароля недостаточно
Хостер отключил сайтОтчёт сканера хостингаПанель хостингаЗаражение уже подтверждено третьей стороной

Четыре независимых уровня проверки сайта на вирусы

Ни один уровень не является полным. Внешний сканер честно скажет «чисто», когда на сервере лежит веб-шелл, потому что шелл не отдаётся по HTTP без специального запроса. Поиск по файлам не найдёт вредонос, который живёт в поле базы данных. Проверка базы не увидит cron-задание, которое каждую ночь заново создаёт удалённые файлы.

Поэтому проверка строится как четыре независимых среза плюс логи как источник ответа на вопрос «как вошли».

УровеньЧто находитЧто пропустит
Снаружи: репутационные базы, сканеры страницДомен в чёрных списках, вредоносный JS в отданном HTML, редиректы, дефейс, фишинговые страницыБэкдоры в PHP, клоакинг под другой User-Agent, инъекции в базе, задания cron, заражение соседних сайтов аккаунта
В браузере: DevTools Network и ConsoleПосторонние домены в запросах, теневые фреймы, ошибки исполнения вставленного кода, подгрузка второй стадииВсё, что не выполняется именно в вашей сессии: серверные закладки, условная выдача, отложенные по времени скрипты
На сервере: файловая системаВеб-шеллы, бэкдоры, дорвей-генераторы, изменённые файлы ядра CMS, посторонние cron-задания, чужие ключи SSHВредонос, который целиком живёт в базе; заражение, подгружаемое с внешнего адреса в момент запроса
В базе данных: контент и опции CMSСкрипты и ссылки в теле материалов, подменённые адреса сайта, лишние администраторы, вредонос в опциях и виджетахФайловые бэкдоры, изменения в шаблонах и в конфигурации веб-сервера
Логи веб-сервера и почтыКак и когда вошли, с каких адресов, какие файлы запрашивали, факт и объём рассылкиВсё, что было до ротации логов; действия через FTP и панель, если их журналы не ведутся

Практический порядок: сначала снаружи (быстро, ничего не ломает), потом браузер, потом сервер, потом база. Логи смотрят параллельно с сервером — они экономят часы, потому что сразу показывают время первого подозрительного запроса, а от него удобно отталкиваться при поиске по mtime.

Уровень 1. Снаружи: репутационные базы и сканеры страниц

Самый быстрый способ — проверить URL через внешние сервисы. Они забирают страницу, разбирают HTML и JavaScript, смотрят, куда уходят запросы, и сверяют домен и подгружаемые ресурсы с базами вредоносных адресов.

  • Google Safe Browsing — базовый ответ на вопрос «покажет ли Chrome красный экран». Проверяется через Transparency Report по адресу сайта.
  • VirusTotal — сканирование URL и файлов десятками антивирусных движков сразу. Полезен тем, что показывает вердикты разных вендоров и историю обращений к домену.
  • Sucuri SiteCheck — проверка на известные шаблоны вредоносных вставок, спам-контент и присутствие в чёрных списках.
  • URLScan.io — не столько вердикт, сколько запись поведения страницы: все запросы, все домены, все ресурсы. Часто именно здесь виден единственный посторонний скрипт.

На enterno.io этот срез закрывает проверка сайта на вредоносный код: она смотрит отданный HTML, внешние скрипты и репутацию домена, а комплексная проверка безопасности добавляет заголовки и типовые ошибки конфигурации.

Что этот уровень принципиально не видит: всё, что сервер не отдал в ответ на обычный запрос. Веб-шелл по адресу вида /wp-content/uploads/2024/06/wp-conf.php будет молчать, пока к нему не обратятся с нужным параметром. Клоакинг покажет чистую страницу любому сканеру, который представился обычным браузером. Задания cron, рассылающие спам, вообще не касаются HTTP.

Вердикт внешнего сканера «чисто» не означает, что сайт не заражён. Он означает, что сайт не заражён видимым снаружи способом прямо сейчас и прямо для этого запроса.

Уровень 2. В браузере: DevTools Network и Console

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

Порядок такой. Откройте инструменты разработчика, вкладку Network, поставьте отключение кеша и перезагрузите страницу. Затем отсортируйте запросы по домену и просмотрите список сверху донизу. Вас интересуют только чужие домены.

  • Незнакомые домены в запросах. Выпишите все домены, кроме своего, аналитики и CDN, которые вы подключали сами. Каждый оставшийся — кандидат на проверку.
  • Сокращатели ссылок и «короткие» домены в цепочке загрузки — почти всегда признак вставки.
  • Цепочки редиректов. Во вкладке Network смотрите ответы 301/302 и заголовок Location. Один лишний переход на чужой домен — это уже находка.
  • Ошибки в Console. Вставленный код часто написан плохо и падает с ошибкой. Сообщение об ошибке при этом содержит адрес файла, где он лежит.
  • Скрытые фреймы нулевого размера или сдвинутые за границы экрана. Их видно во вкладке Elements по нулевым размерам и по чужому адресу источника.

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

Быстрый способ собрать список внешних источников без браузера — вытащить их прямо из HTML.

# Все внешние скрипты и фреймы на главной странице
curl -sL https://example.com/ \
  | grep -oE '(src|href)="https?://[^"]+"' \
  | grep -v 'example\.com' \
  | sort -u

# Тот же список, но с мобильным User-Agent — сравните с предыдущим
curl -sL -A "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1" \
  https://example.com/ \
  | grep -oE '(src|href)="https?://[^"]+"' \
  | grep -v 'example\.com' \
  | sort -u

Если два списка различаются — сайт отдаёт разный код разным клиентам. Это уже почти доказанный клоакинг, и к нему мы вернёмся отдельно. Заодно посмотрите, какой стек реально работает на сайте — определение технологий иногда показывает библиотеку или счётчик, которые вы не подключали.

Уровень 3. На сервере: файловая система

Это главный уровень. Всё остальное — косвенные признаки, а здесь находится сам вредонос. Работать нужно по SSH; через FTP-клиент искать бесполезно, потому что вы не сможете ни отсортировать тысячи файлов по времени изменения, ни выполнить поиск по содержимому.

Свежие файлы по времени изменения

Взлом почти всегда оставляет следы в mtime. Если известно примерное время инцидента из логов — ищите вокруг него; если нет — начните с последних суток и расширяйте окно.

# Всё, что изменилось за последние 3 суток (кроме кеша и логов)
find /var/www/example.com -type f -mtime -3 \
  -not -path "*/cache/*" -not -path "*/logs/*" \
  -printf '%TY-%Tm-%Td %TH:%TM  %p\n' | sort

# Файлы, изменённые ПОЗЖЕ конкретной даты и времени
find /var/www/example.com -type f -newermt "2026-08-01 03:00" \
  -not -path "*/upload/*" -ls

# Изменённые за окно между двумя моментами
find /var/www/example.com -type f \
  -newermt "2026-08-01 02:00" ! -newermt "2026-08-01 06:00" -ls

Важная оговорка: mtime можно подделать вызовом touch, и грамотные закладки это делают, выставляя дату «как у соседних файлов». Поэтому дополнительно смотрят ctime — время изменения метаданных инода, которое подделать из PHP нельзя.

# Файлы с недавним ctime — ловит подделку mtime через touch
find /var/www/example.com -type f -ctime -7 \
  -not -path "*/cache/*" -ls | head -100

Файлы не на своём месте

Второй по надёжности признак — исполняемый файл там, где его быть не должно. В каталоге загрузок лежат картинки и документы, а не PHP. В каталоге картинок шаблона — тем более.

# PHP в каталогах загрузок и картинок — почти всегда закладка
find /var/www/example.com/wp-content/uploads -type f \
  \( -name "*.php" -o -name "*.phtml" -o -name "*.php[0-9]" -o -name "*.phar" \) -ls

find /var/www/example.com/upload -type f -name "*.ph*" -ls

# Двойные расширения: картинка, которая на самом деле скрипт
find /var/www/example.com -type f -regex '.*\.\(jpg\|png\|gif\|webp\)\.php$' -ls

# Файлы с правами 777 и каталоги 777
find /var/www/example.com -type f -perm 0777 -ls
find /var/www/example.com -type d -perm 0777 -ls

# Имена с непечатаемыми символами и пробелами на конце
find /var/www/example.com -name '*[[:cntrl:]]*' -o -name '* '

Сигнатуры обфускации

Вредоносный PHP почти всегда прячет полезную нагрузку: строка с кодом упакована и разворачивается в момент выполнения. Именно упаковка и выдаёт закладку — нормальный код темы или модуля так не пишут.

Ключевой момент: базовый grep не понимает вертикальную черту как «или». Команда вида grep -rl "eval|system" ищет литеральную строку с чертой внутри и не находит ничего, создавая ложное ощущение чистоты. Нужен ключ -E.

# Поиск сигнатур обфускации и веб-шеллов (обязательно -E!)
grep -rlE --include="*.php" --include="*.phtml" --include="*.inc" \
  'eval[[:space:]]*\(|base64_decode[[:space:]]*\(|gzinflate[[:space:]]*\(|gzuncompress[[:space:]]*\(|str_rot13[[:space:]]*\(|assert[[:space:]]*\(|create_function[[:space:]]*\(|preg_replace[[:space:]]*\(.*/e|passthru[[:space:]]*\(|shell_exec[[:space:]]*\(|popen[[:space:]]*\(|proc_open[[:space:]]*\(' \
  /var/www/example.com

# Обращения к суперглобалам напрямую в исполнение — типичный шелл
grep -rnE --include="*.php" \
  '\$_(GET|POST|REQUEST|COOKIE)\[[^]]*\][[:space:]]*\)' /var/www/example.com | head -50

# Строки, собранные посимвольно, чтобы обойти поиск по имени функции
grep -rlE --include="*.php" '(\\x[0-9a-fA-F]{2}){4,}|(chr\([0-9]+\)\.){3,}' /var/www/example.com

# Однострочники длиной больше 800 символов — почти всегда упакованная нагрузка
grep -rlE --include="*.php" '.{800,}' /var/www/example.com

Дальше найденные файлы разбирают вручную. Ориентир, что означает каждая сигнатура:

СигнатураПочему подозрительно
eval(base64_decode(...))Классическая связка: закодированная строка декодируется и тут же исполняется. В нормальном коде CMS не встречается
gzinflate, gzuncompress, str_rot13 подрядМногослойная упаковка — нагрузку разворачивают в несколько проходов, чтобы не срабатывал поиск по строкам
assert($_REQUEST[...])До PHP 8 функция assert исполняла строку как код. Веб-шелл в одну строку
preg_replace с модификатором /eУстаревшая форма выполнения кода из замены. Признак старой закладки или старого уязвимого движка
create_functionДинамическое создание функции из строки. Удалено в PHP 8, в современном коде быть не должно
Эскейпы вида \x65\x76Имя функции собрано посимвольно, чтобы grep по слову ничего не нашёл
Конкатенация chr(101).chr(118)Тот же приём другим способом
Однострочник на несколько килобайтУпакованная нагрузка. Нормальный код так не форматируют
file_get_contents или curl_exec к внешнему адресу в шаблонеЗагрузчик второй стадии: тело вредоноса подтягивается снаружи и не хранится на сервере
Переменные с непечатаемыми именами в $GLOBALSПриём сокрытия: имя переменной нечитаемо и не ищется поиском

Осторожно с ложными срабатываниями. base64_decode легально встречается в библиотеках работы с почтой, JWT и изображениями. Сигнатура — это повод открыть файл, а не приговор. Приговор выносит сверка с эталоном.

Сверка с эталоном: git, контрольные суммы, diff

Самый надёжный способ отличить свой файл от чужого — сравнить с заведомо чистой копией. Здесь не нужны эвристики: либо файл совпадает с оригиналом, либо нет.

# Если код под git — самое быстрое: что изменилось и что появилось
cd /var/www/example.com && git status --porcelain
git diff --stat
git clean -nd            # что появилось лишнего, без удаления

# WordPress: сверка ядра с официальными контрольными суммами
wp core verify-checksums --path=/var/www/example.com
wp plugin verify-checksums --all --path=/var/www/example.com

# Универсальный способ: скачать чистый дистрибутив той же версии и сравнить
cd /tmp && curl -sO https://ru.wordpress.org/wordpress-6.8.2-ru_RU.zip
unzip -q wordpress-6.8.2-ru_RU.zip -d /tmp/clean
diff -rq /tmp/clean/wordpress/wp-includes /var/www/example.com/wp-includes
diff -rq /tmp/clean/wordpress/wp-admin    /var/www/example.com/wp-admin

# Снимок контрольных сумм «до», чтобы в следующий раз сравнение заняло минуту
find /var/www/example.com -type f -name "*.php" -exec sha256sum {} \; \
  | sort -k2 > /root/checksums-$(date +%F).txt
diff /root/checksums-2026-07-01.txt /root/checksums-2026-08-01.txt

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

Cron, ключи SSH и лишние пользователи

Часто самая неприятная часть заражения — не файл, а механизм его восстановления. Вы удаляете шелл, а через час он появляется снова, потому что его каждый час пересоздаёт задание планировщика.

# Планировщик всех пользователей
for u in $(cut -f1 -d: /etc/passwd); do echo "== $u"; crontab -l -u "$u" 2>/dev/null; done
ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/
grep -rn "curl\|wget\|php -r\|base64" /etc/cron.d/ /var/spool/cron/ 2>/dev/null

# Задания systemd, добавленные не вами (таймеры и юниты)
systemctl list-timers --all
ls -la /etc/systemd/system/ | grep -v '^total'

# Чужие ключи SSH
for f in /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys; do
  echo "== $f"; cat "$f" 2>/dev/null
done

# Пользователи с UID 0 и с валидной оболочкой, созданные недавно
awk -F: '$3 == 0 {print}' /etc/passwd
grep -E '/bin/(ba)?sh$' /etc/passwd
ls -la --time-style=long-iso /home

Типовые места закладок в WordPress и Битриксе

Злоумышленники предсказуемы: они кладут закладки туда, где их не смотрят и откуда код гарантированно выполнится.

WordPress. Каталог wp-content/mu-plugins — плагины, которые подключаются всегда и не отображаются в обычном списке плагинов; их надо смотреть в первую очередь. Файлы functions.php активной и, что важнее, неактивных тем. Файл wp-config.php и подключения перед объявлением констант. Корневые index.php, wp-load.php, wp-settings.php, куда добавляют одну строку include. Каталог wp-content/uploads с датированными подпапками, где PHP-файл выглядит как ещё одно вложение. Плюс таблица wp_options — про неё в разделе про базу.

1С-Битрикс. Файл bitrix/php_interface/init.php и одноимённый файл в каталоге сайта — они выполняются на каждом хите. Каталог bitrix/php_interface/ целиком. Шаблоны сайта в local/templates/ и bitrix/templates/, особенно header.php и footer.php. Каталог upload/, в котором исполнение PHP должно быть выключено на уровне веб-сервера. Каталог bitrix/modules/ — сюда добавляют «свой модуль» с безобидным именем. И .htaccess в корне и в подкаталогах: правила условного редиректа прячут в самый низ файла, после десятков строк стандартной конфигурации.

Отдельная проверка для обеих CMS — сам .htaccess и конфигурация nginx: правило вида «отдавать другой файл, если запрос пришёл с определённым Referer» не является вредоносным кодом, но делает ровно то же самое.

Схема поиска бэкдора на сервере: свежие файлы по mtime, PHP в каталоге загрузок, обфускация и сверка с эталоном
Четыре независимых признака на файловой системе: время изменения, неправильное место, обфускация и расхождение с эталоном

Уровень 4. В базе данных: инъекции в контент и опции CMS

Часть заражений вообще не трогает файлы. Вредоносная вставка живёт в поле базы данных, а CMS честно выводит её на страницу, потому что для CMS это обычный контент.

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

# WordPress: скрипты и фреймы в теле записей и страниц
SELECT ID, post_title, post_status FROM wp_posts
WHERE post_content LIKE '%<scr%ipt%'
   OR post_content LIKE '%<ifr%ame%'
   OR post_content LIKE '%display:none%'
   OR post_content LIKE '%base64_%';

# Подменённый адрес сайта — типовой приём для перехвата трафика
SELECT option_name, option_value FROM wp_options
WHERE option_name IN ('siteurl','home','users_can_register','default_role');

# Автозагружаемые опции аномального размера — там прячут нагрузку
SELECT option_name, LENGTH(option_value) AS len FROM wp_options
WHERE autoload = 'yes' ORDER BY len DESC LIMIT 20;

# Администраторы и недавно созданные учётные записи
SELECT u.ID, u.user_login, u.user_email, u.user_registered
FROM wp_users u
JOIN wp_usermeta m ON m.user_id = u.ID
WHERE m.meta_key = 'wp_capabilities' AND m.meta_value LIKE '%administrator%';

Для 1С-Битрикс логика та же, отличаются таблицы: тело материалов — b_iblock_element (поля с описанием) и свойства элементов, служебные значения — b_option, пользователи и группы — b_user и b_user_group. Отдельно проверьте таблицу с шаблонами писем: рассылку спама часто организуют через штатный почтовый механизм CMS, подменив шаблон.

# Битрикс: посторонний код в описаниях элементов инфоблоков
SELECT ID, IBLOCK_ID, NAME FROM b_iblock_element
WHERE DETAIL_TEXT LIKE '%<scr%ipt%' OR PREVIEW_TEXT LIKE '%<scr%ipt%';

# Пользователи в группе администраторов и время последней авторизации
SELECT u.ID, u.LOGIN, u.EMAIL, u.DATE_REGISTER, u.LAST_LOGIN
FROM b_user u JOIN b_user_group g ON g.USER_ID = u.ID
WHERE g.GROUP_ID = 1;

Перед любым запросом на изменение снимите дамп базы. Восстановить неудачную массовую замену в post_content без дампа невозможно, а «почищу заодно» — самая частая причина потери контента при лечении.

Клоакинг: почему сайт чистый для вас и заражённый для Google

Самая частая причина ситуации «сканер молчит, а пользователи жалуются» — клоакинг. Закладка проверяет, кто пришёл, и отдаёт вредонос только выбранной аудитории. Логика у злоумышленника простая: чем реже владелец видит проблему, тем дольше живёт заражение.

Типовые условия, по которым выбирается жертва:

  • User-Agent поисковых роботов. Дорвей отдаётся googlebot и yandexbot, чтобы страницы попали в индекс, а обычному посетителю показывается нормальный сайт.
  • Мобильные User-Agent. Редирект срабатывает только на телефонах — владелец сидит за десктопом и ничего не видит.
  • Referer из поисковой системы. Пришёл из поиска — редирект; открыл сайт по закладке или ввёл адрес руками — всё нормально. Владелец почти всегда заходит вторым способом.
  • Первый визит. После срабатывания ставится cookie, и повторно редирект не выполняется. Поэтому «у меня было один раз, а теперь нет» — не признак излечения.
  • IP и геолокация. Вредонос не отдаётся адресам хостинга, диапазонам поисковиков или стране владельца.

Проверяется это подменой заголовков в curl. Ключ -A задаёт User-Agent, ключ -e — Referer, -I запрашивает только заголовки, а -L заставляет пройти по всей цепочке редиректов.

# 1. Обычный десктопный браузер — эталон
curl -sSIL -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0 Safari/537.36" \
  https://example.com/ | grep -Ei 'HTTP/|^location:'

# 2. Робот Google
curl -sSIL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
  https://example.com/ | grep -Ei 'HTTP/|^location:'

# 3. Робот Яндекса
curl -sSIL -A "Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)" \
  https://example.com/ | grep -Ei 'HTTP/|^location:'

# 4. Мобильный браузер
curl -sSIL -A "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1" \
  https://example.com/ | grep -Ei 'HTTP/|^location:'

# 5. Переход из поиска: подменяем Referer
curl -sSIL -e "https://www.google.com/search?q=example" \
  https://example.com/ | grep -Ei 'HTTP/|^location:'

curl -sSIL -e "https://yandex.ru/search/?text=example" \
  https://example.com/ | grep -Ei 'HTTP/|^location:'

Если хотя бы один вариант выдал иной статус или иной заголовок Location — клоакинг подтверждён. Дальше сравнивают уже тела ответов: так видно вставку, которая не редиректит, а подмешивает ссылки.

# Сравнение тел ответа для робота и для человека
curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
  https://example.com/ > /tmp/bot.html
curl -sL -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/128.0 Safari/537.36" \
  https://example.com/ > /tmp/human.html
diff <(sed 's/>/>\n/g' /tmp/bot.html) <(sed 's/>/>\n/g' /tmp/human.html) | head -60

# Заодно: сколько внешних ссылок отдаётся роботу
grep -oE 'href="https?://[^"]+"' /tmp/bot.html | grep -v example.com | sort | uniq -c | sort -rn | head

Дополнительный сигнал того же рода — резкий рост числа битых ссылок: генератор дорвеев наплодил страницы, они попали в индекс и в перелинковку, а потом были удалены. Пройтись по сайту помогает проверка битых ссылок: если она находит сотни адресов, которых вы не создавали, вопрос о заражении можно считать закрытым.

Схема клоакинга: один сервер отдаёт чистую страницу владельцу и вредоносную страницу поисковому роботу и мобильному посетителю
Клоакинг: ответ зависит от User-Agent, Referer и cookie — поэтому владелец видит чистый сайт

Смежные проверки: HTTP-заголовки, SSL/TLS и mixed content

Эти проверки не находят вирус, но показывают, насколько легко его туда занести и насколько заметно он там будет.

Заголовки безопасности. Отсутствие заголовков — не заражение, а открытая дверь. Минимальный набор:

  • Content-Security-Policy — ограничивает источники скриптов. Правильно настроенный CSP не даёт исполниться внедрённому скрипту с чужого домена и попутно фиксирует попытку в отчёте. Это единственный заголовок, который реально мешает заражению работать; проверить его удобно через анализатор CSP.
  • X-Frame-Options или директива frame-ancestors в CSP — защита от кликджекинга.
  • X-Content-Type-Options: nosniff — запрет угадывания типа содержимого. Без него загруженная «картинка» с кодом внутри может быть интерпретирована как скрипт.
  • Strict-Transport-Security — принудительный HTTPS для всех последующих визитов.

Разобрать фактический набор заголовков ответа можно через проверку HTTP-заголовков, а сводную оценку конфигурации даёт проверка безопасности сайта.

SSL и mixed content. Здесь важно снять устаревшее заблуждение: наличие валидного сертификата давно не является признаком безопасности сайта. Бесплатные сертификаты выдаются автоматически, и у фишинговой страницы почти всегда есть корректный HTTPS. Замок в адресной строке говорит только о том, что соединение шифруется, а не о том, что на другом конце добросовестный сайт.

Что действительно стоит проверить на своём сайте: срок действия и автопродление, полноту цепочки, поддержку современных версий протокола и отсутствие смешанного содержимого. Последнее прямо связано с темой: появление на HTTPS-странице ресурсов, загружаемых по HTTP, — частый побочный эффект внедрённого кода. Помогают проверка SSL-сертификата, разбор ошибок SSL и поиск mixed content.

Чёрные списки и предупреждения браузера: как проверить и как выйти

Списков несколько, и они независимы. Выход из одного не снимает пометку в другом.

  • Google Safe Browsing. Отвечает за красный экран в Chrome и в браузерах на его движке, а также за пометку в выдаче. Проверяется через Transparency Report и раздел «Проблемы безопасности» в Search Console.
  • Яндекс. Раздел «Безопасность и нарушения» в Вебмастере: показывает найденный вредоносный код с указанием страниц.
  • DNSBL и списки репутации IP. Влияют в первую очередь на почту: письма с вашего сервера перестают доходить. На общем хостинге в список часто попадает не ваш сайт, а сосед по IP.
  • Списки вендоров антивирусов и браузерных фильтров. Реагируют на подгружаемые вами домены, а не только на ваш собственный.
  • Реестр запрещённой информации. Отдельная от безопасности история, но с тем же практическим эффектом — недоступность у части провайдеров; проверяется через проверку блокировок.

Порядок выхода одинаков для всех списков и состоит из трёх шагов, которые нельзя менять местами. Сначала полностью устраняется причина — не «удалил найденный файл», а вычищен весь вредонос и закрыта уязвимость. Затем подаётся запрос на перепроверку: в Search Console это кнопка в разделе проблем безопасности, в Вебмастере — аналогичная в разделе нарушений. И только потом ждут: срок зависит от очереди на стороне поисковика и обычно измеряется днями, а не часами. Точных сроков никто не гарантирует.

Повторный запрос на перепроверку при неустранённой причине хуже, чем отсутствие запроса: сайт помечается снова, а доверие к домену падает. Проверьте себя внешним сканером до подачи заявки.

Более подробно процедура снятия пометок разобрана в материале о проверке сайта в чёрных списках, а признаки мошеннических и фишинговых страниц — в разборе проверки сайта на мошенничество.

План лечения заражённого сайта по шагам

Порядок здесь важнее скорости. Почти все неудачные лечения — это перепутанные шаги: сначала восстановили из бэкапа, потом сменили пароли, а уязвимость не трогали вовсе.

  1. Снимите риск для посетителей. Закройте сайт заглушкой или отдавайте 503 всем, кроме своего адреса. Пока сайт раздаёт вредонос, растёт число пострадавших и глубина санкций.
  2. Сделайте полную копию ТЕКУЩЕГО заражённого состояния. Файлы и дамп базы. Это не бэкап для восстановления, это материал для расследования: по нему вы найдёте точку входа. Без него после чистки вы не узнаете, как вас взломали.
  3. Соберите логи. Access- и error-логи веб-сервера, логи почты, панели, FTP и SSH. Их ротация уничтожит следы быстрее, чем вы дойдёте до анализа.
  4. Смените все секреты сразу. Пароли SSH и FTP, пароль пользователя базы, пароли всех администраторов CMS, ключи API, токены интеграций, соль и ключи в конфигурации CMS. Удалите чужие ключи из authorized_keys. Пароль от почты, привязанной к админке, тоже.
  5. Найдите точку входа по логам. Ищите первый запрос к файлу, которого не должно существовать, POST-запросы к нетипичным адресам, обращения к загрузчику и всплеск запросов с одного адреса. Время первого такого запроса задаёт окно для поиска по mtime.
  6. Восстановитесь из заведомо чистого бэкапа. Ключевое слово — «заведомо». Вчерашний бэкап заражённого сайта содержит тот же бэкдор. Отталкивайтесь от даты, установленной на шаге 5, и берите копию заведомо раньше её. Если такой копии нет — переходите к чистке вручную.
  7. Переустановите ядро CMS и все расширения поверх. Скачайте дистрибутивы той же или более новой версии с официальных источников и замените каталоги ядра целиком. Это дешевле и надёжнее, чем чистить файлы ядра по одному. Пользовательский контент и загрузки при этом не трогают, но проверяют отдельно.
  8. Вычистите базу. По результатам проверки уровня 4: удалите лишних администраторов, верните корректные адреса сайта, уберите вставки из контента, проверьте автозагружаемые опции и шаблоны писем.
  9. Закройте исходную уязвимость. Обновите CMS, темы и модули до актуальных версий. Удалите то, чем не пользуетесь: неактивная тема с дырой эксплуатируется так же, как активная. Запретите исполнение PHP в каталогах загрузок. Ограничьте доступ к админке по адресу или вторым фактором. Настройте блокировку перебора — например, через fail2ban. Остальные меры уровня сервера собраны в материале о харденинге веб-сервера.
  10. Проверьте результат и только потом открывайте. Прогоните сайт всеми четырьмя уровнями заново, проверьте клоакинг подменой User-Agent, убедитесь, что в индексе нет чужих страниц. После этого снимайте заглушку и подавайте запросы на перепроверку в панели вебмастеров.

Шаг 2 пропускают чаще всего — и потом не могут ответить на вопрос «как это произошло». Копия заражённого состояния занимает несколько минут и стоит гигабайт места, а её отсутствие означает повторное заражение вслепую.

Почему «удалил вирус» без закрытия дыры — это повтор через неделю

Механизм повторного заражения устроен банально. Уязвимость, через которую вошли, никуда не делась: тот же устаревший модуль, та же форма загрузки без проверки типа, тот же слабый пароль. Сканеры злоумышленников работают непрерывно и находят ваш сайт снова — обычно быстрее, чем в первый раз, потому что адрес уже в их базе как успешная цель.

Есть и вторая причина. Заражение почти никогда не состоит из одного файла: рядом лежат резервные закладки, задание планировщика для восстановления, изменённый файл ядра с одной строкой include и админская учётная запись про запас. Удаление «того самого» файла убирает симптом, а не механизм.

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

Практический критерий готовности: вы можете назвать конкретную уязвимость, конкретную дату и конкретный запрос из лога, через который вошли. Если не можете — дыра не закрыта, и вопрос повтора — вопрос времени. Что делать сразу после обнаружения взлома, подробно разобрано в материале о действиях при взломе сайта, а формализовать реакцию помогает план реагирования на инциденты.

Пошаговый план лечения заражённого сайта: изоляция, копия для расследования, смена секретов, чистая копия, закрытие уязвимости
Порядок лечения: снять риск, сохранить улики, сменить секреты, восстановить из чистой копии, закрыть дыру — и только потом открыть сайт

Профилактика: мониторинг изменений файлов и что настроить один раз

Заражение обнаруживается либо в первый час, либо через месяц — промежуточных вариантов почти не бывает. Разница целиком определяется тем, настроен ли контроль изменений.

  • Снимок контрольных сумм по расписанию. Ежедневный sha256sum по каталогу сайта с автоматическим сравнением с предыдущим снимком и письмом при расхождении. Это пятнадцать строк скрипта и самая эффективная мера из доступных без бюджета.
  • Код под системой контроля версий. Если каталог сайта — рабочая копия git, то git status отвечает на вопрос «что изменилось» за секунду. Загрузки и кеш при этом держат вне репозитория.
  • Запрет исполнения PHP в каталогах загрузок на уровне веб-сервера. Отдельное правило для uploads и upload обесценивает целый класс атак через формы загрузки файлов.
  • Разделение прав. Веб-сервер не должен иметь права записи в файлы кода: писать ему нужно только в каталоги загрузок, кеша и логов. Права 777 не нужны никогда.
  • Раздельные аккаунты для разных сайтов. Один взломанный сайт не должен видеть файлы соседнего. Это вопрос конфигурации хостинга, а не CMS.
  • Бэкапы с историей и вне сервера. Одна вчерашняя копия бесполезна: заражение старше суток. Нужна глубина в несколько недель и хранение отдельно от продакшена — подробнее в руководстве по резервному копированию.
  • Мониторинг доступности и содержимого страницы. Проверка не только кода ответа, но и наличия контрольной строки в HTML: подмена главной страницы обнаруживается сразу.
  • Регулярный внешний скан. Раз в неделю прогонять сайт по репутационным базам — дешевле, чем узнавать о пометке от клиентов.

Отдельно про уязвимости приложения: значительная часть заражений начинается не с подбора пароля, а с внедрения кода через форму или параметр. Базовая гигиена вывода и экранирования разобрана в материале о защите от XSS, а общий список проверок — в чеклисте проверки безопасности сайта.

Как проверить на enterno.io

Частые вопросы

Я хотел узнать, безопасен ли чужой сайт, а не свой. Что делать?

Проверьте домен по репутационным базам: Google Safe Browsing, VirusTotal и сканер вредоносного кода дают ответ за секунды и не требуют доступа к сайту. Дополнительно посмотрите, как давно зарегистрирован домен, через whois: свежая регистрация плюс страница с оплатой — типичное сочетание для фишинга. Валидный сертификат HTTPS ничего не гарантирует. Отдельный разбор признаков — в материале о проверке сайта на мошенничество. И помните: файл, скачанный с незнакомого сайта, проверяется уже локальным антивирусом, а не сканером сайтов.

Можно ли проверить сайт на вирусы онлайн, без доступа к серверу?

Частично. Онлайн-сканеры находят вредоносный JavaScript в отданном HTML, редиректы, дефейс и присутствие домена в чёрных списках. Они не находят PHP-бэкдоры, инъекции в базе и задания планировщика, потому что этого нет в HTTP-ответе. Если сканер молчит, а симптомы есть — вопрос закрывается только доступом к серверу.

Внешний сканер говорит «чисто», но посетителей уводит на другой сайт. Как так?

Это клоакинг. Вредонос отдаётся по условию — мобильным, при переходе из поиска или только при первом визите, а сканеру, который пришёл обычным браузером с сервера в дата-центре, показывается чистая страница. Проверяется подменой User-Agent и Referer в curl, как показано выше.

Хостер прислал письмо, что нашёл вредоносный файл. Достаточно его удалить?

Нет. Хостер сообщает о том, что сработала его сигнатура на одном файле, а не о том, что это единственный файл. Рядом почти всегда есть резервные закладки, задание cron для восстановления и учётная запись администратора про запас. Удаление одного файла убирает срабатывание сканера, а не заражение.

Помогает ли смена пароля от админки?

Как отдельная мера — почти нет. Если у злоумышленника уже есть файловый бэкдор, пароль ему не нужен: код выполняется в обход авторизации. Смена паролей обязательна, но работает только в комплекте с чисткой файлов и закрытием уязвимости, и менять надо все секреты сразу, включая доступ к базе и ключи API.

Как понять, когда именно взломали сайт?

По логам и по времени изменения файлов. В access-логе ищут первый запрос к несуществующему ранее файлу или нетипичный POST; полученное время задаёт окно для поиска через find -newermt. Если mtime подделан вызовом touch, ориентируйтесь на ctime — его из PHP изменить нельзя.

Через сколько снимут предупреждение браузера после чистки?

После устранения причины и запроса на перепроверку в панели вебмастера пометка снимается обычно в течение нескольких дней. Точные сроки не гарантируются и зависят от очереди на стороне поисковой системы. Подавать запрос до фактической чистки бессмысленно и вредно: повторная пометка ухудшает доверие к домену.

Нужен ли платный сканер, если есть бесплатные?

Бесплатных инструментов достаточно, чтобы обнаружить заражение и вылечить его вручную: доступ по SSH, find, grep -E, сверка контрольных сумм и внешние репутационные базы закрывают все четыре уровня. Платные решения экономят время на рутине и дают постоянный контроль изменений, но не находят принципиально другого класса угроз.

Чеклист проверки сайта на вирусы

  • Проверен внешний срез: репутационные базы, сканер страниц, разделы безопасности в панелях вебмастеров.
  • Открыты DevTools: во вкладке Network нет посторонних доменов, в Console нет ошибок от чужого кода.
  • Проверен клоакинг: ответы для десктопа, мобильного, googlebot, yandexbot и при переходе из поиска совпадают.
  • Найдены и разобраны файлы, изменённые за окно инцидента, по mtime и по ctime.
  • В каталогах загрузок нет исполняемых файлов, двойных расширений и файлов с непечатаемыми именами.
  • Выполнен поиск сигнатур обфускации с ключом -E, а не с обычным grep.
  • Ядро CMS и расширения сверены с эталоном: контрольные суммы, git status или diff с чистым дистрибутивом.
  • Проверены планировщик, systemd-таймеры, authorized_keys и список пользователей системы.
  • Проверена база: контент материалов, адреса сайта, автозагружаемые опции, администраторы, шаблоны писем.
  • Собраны логи веб-сервера и почты до их ротации; найдена точка входа.
  • Сменены все секреты: SSH, FTP, база, администраторы CMS, ключи API, соль в конфигурации.
  • Уязвимость закрыта: обновления установлены, неиспользуемые расширения удалены, исполнение PHP в загрузках запрещено.
  • Восстановление сделано из заведомо чистой копии, а не из вчерашней.
  • Настроен контроль изменений файлов и мониторинг содержимого страницы.
  • Запросы на перепроверку поданы только после фактического устранения причины.

Проверьте ваш сайт прямо сейчас

Проверить безопасность сайта →
Другие статьи: Безопасность
Безопасность
Сайт или IP в чёрном списке: как проверить и выйти
21.07.2026 · 1 554 просм.
Безопасность
Правила WAF: написание эффективных политик веб-файрвола
16.03.2026 · 945 просм.
Безопасность
Заголовки безопасности: CSP, HSTS, X-Frame-Options и другие
10.03.2025 · 695 просм.
Безопасность
Сайты, заблокированные Роскомнадзором: разбор 131 000 доменов
26.06.2026 · 588 просм.