[recovery mode] Сказ о том, как я с гидрой боролся
Проблемы третьего мира
Вообще на этом фриланс-проекте я занимался вёрсткой — простенький дизайн в Figma, где ничто не предвещало беды. И вот, когда проект сдан, заказчик внезапно присылает скриншоты с Mac и iPhone — и там какие-то белые квадраты, дизайном ни разу не предусмотренные.
Вариант «а у меня всё нормально показывется» не прокатил. Пришлось поднять виртуалку с MacOS и убедиться: квадраты есть. Консоль? Есть консоль! В консоли — ошибки, какие-то бредовые скрипты, пытающиеся (неудачно) показать рекламу. Заказчик клянётся и божится, что ничего такого не ставил… Смотрю исходники, вижу прекрасный /index.php
вот такого содержания:
И тут вспоминаю, что когда возился с настройкой главного меню (да, в Битриксе с этим приходится возиться), видел странную строку: include($_SERVER["DOCUMENT_ROOT"]."/upload/dr/content/inc.php");
Для Битрикса она довольно нормальная, антивирусы тоже спокойно пропускают. А мне как-то странно — ну кто в здравом уме подключает файлы из upload?
Небольшая справка для тех, кто не знаком с Битриксом и верит в здравый ум:
У него есть несколько проблем:Сайт — это примерно 12.000 мелких php-файлов, раскиданных по сотням директорий разных уровней, каждая страница — отдельный php-файл, каждый компонент — отдельная папка с кучей файлов. Единой точки входа нет, разделения M/V/C нет, растительности нет, жизни нет, населена роботами.
CSS и JS раскиданы по всему сайту, и на лету собираются в единую свалку. Управлять этим невозможно, поэтому практикуется встраивание inline-картинок в css, css в html, html в php, php подключаются как попало (include, include_once, require …) и куда попало.
шаблоны вёрстки — php-файлы, в которых код перемешан с html, да ещё и по задумке разработчиков раскиданы по такой структуре директорий (3 раза bitrix — это не предел!):
/bitrix/components/bitrix/iblock.element.add/templates/.default/bitrix/iblock.element.add.form/.default/template.php
Но если вы думаете, что мучения по поиску нужного компонента заканчиваются на файле
/bitrix/components/bitrix/news/templates/.default/bitrix/catalog.filter/.default/template.php
то вы ошибаетесь — нужный вам файл может переопределяться вот этим:/local/templates/mystyle/components/bitrix/news/certificates/bitrix/catalog.filter/.default/template.php
Разработчики часто бывают не очень грамотные и путаются в этих путях. Плюс официальные обновления иногда ломают совместимость. Поэтому при обновлении движка часто ломается весь сайт, и то, что работает пару лет без обновлений, трогать крайне рискованно. Естественно, дыры в безопасности копятся годами.
Со всем этим безобразием всё равно приходится работать, потому что на предложение «снести сайт к чертям и сделать нормально» малый бизнес реагирует довольно болезненно.
Археология
Итак, Битрикс во всей своей красе и первозданной мощи. И где-то в этой адской машине — вирус, плавно жрущий сайт: пока разбирались, что к чему, стали появляться какие-то левые файлы. Их удаляешь, а они снова появляются. Всю дрянь из скрипта вычистил -, а через час она снова на месте, и ещё в паре мест.
По идее, для таких вещей есть антивирусы типа VirusDie, но на практике они не работают — тот самый include из папки upload он, например, подозрительным не считает. А от беглого просмотра создатели вируса предусмотрели забавную, но тупую защиту:
Защита от беглого взгляда — 3 МбКак известно, глупые и ленивые всякую работу делают дважды. Поэтому сначала я попытался вычистить всё это вручную, в надежде, что там всего пара «закладок» и сейчас я всё починю. Но гидра не сдавалась, новые головы отрастали на месте отрубленных и приводили меня в отчаяние.
На сервере появлялись новые файлы с stupidcode shell, why bro shell и прочими говорящими названиями. Любопытными при этом представлялись два факта:
самые старые из найденных файлов вируса датировались 2016-м годом, а попытки лечить его через VirusDie предпринимались с 2015-го!
файлы появлялись непонятным образом, всё, что можно было вычистить, было вычищено
Снежное чучело
На этом моменте (только тогда!) получилось добыть у заказчика ssh-доступ. И тут выяснилось самое интересное. Будет уместна цитата из «Отеля «У погибшего альпиниста», полностью описывающая моё состояние:
… Он не пошевелился. Тогда я подбежал к нему и потряс за плечо. Я обалдел. Хинкус вдруг как-то странно осел, мягко подавшись у меня под рукой. — Хинкус! — растерянно закричал я, непроизвольно подхватывая его. Шуба раскрылась, из нее вывалилось несколько комьев снега, свалилась меховая шапка, и только тогда я понял, что Хинкуса нет, а есть только снежное чучело, облаченное в его шубу…
Я тоже обалдел. На хостинге был 21 сайт, а у скриптов не было никаких ограничений по доступу между ними — то есть любой скрипт спокойно читал любой файл из директорий выше уровнем (логи, бекапы…) и писал что угодно в другие папки. Фактически, поймав вирус на одном дырявом сайте, ты ловил его сразу на всех! Снеговик, облачённый в меховую шубу — прекрасная метафора уровня безопасности этого проекта, не правда ли?
21 сайт … 250.000 скриптов … за что обижать бедный верстальщик, мистер? я когда-то жить город и ходить школа, мистер, но я не уметь читать так mucho mucho rapido!
Единственное, что спасало — это то, что вирусы явно писали не особо заморачиваясь, видимо, рассчитывая на то, что их никто лечить и не будет. Но мне стало любопытно, смогу ли я с этим справиться.
Плюс автоматизация всей страны
Для начала я с особым цинизмом заблокировал доступ к сайту со стороны Amazon AWS, откуда по логам шла основная коммуникация с вирусными файлами. Кстати, если посоветуете удобную бесплатную программу под Windows для анализа логов — буду рад, платить за http Logs Viewer мне не захотелось, но удобнее ничего не нашёл.
Для получения списка адресов AWS пришлось скачать и установить AWSPowerShell и запустить в PS волшебную команду Get-AWSPublicIpAddressRange | where {$_.IpAddressFormat -eq "Ipv4"} | select IpPrefix
Команда выдаёт список из 3000+ адресов, дополнил его парой литовских серверов, которые уж слишком упорно лезли обращаться к вирусным файлам и закинул в .htaccess (наверное, нормальные админы приготовили для меня отдельный котёл в аду за такое, но ничего умнее на shared хостинге мне в голову не пришло). php_value open_basedir
отправился туда же.
php_value open_basedir "/path/to/site"
Require all granted
Require not ip 100.20.0.0
Require not ip 100.24.0.0
#... 3000+ адресов....
А дальше пришлось писать утилиту, которая бы помогла бы мне обуздать эту гидру. Хотелось сделать быстро, одним файлом, без использования внешних зависимостей, чтобы его было легко просто скопировать в нужное место и запустить, из командной строки или через веб.
Кто первый VSCode запустит, тот и программист
В общем, эта поделка вряд ли порадует кого-то хорошим исходником или правильным программированием, зато она работает и делает то, для чего предназначена — сканирует все файлы на предмет 20+ факторов опасности, взвешивает и выдаёт общую оценку опасности файла.
Признаки разные:
использования более-менее безобидных, но опасных функций
eval()
иshell_execute()
характерных паттерны обфускации кода вроде
$WUGh64382 = irmcjaowlfxc($gbVclGM7976[39]);
длинные цепочки вроде
\144\151\x73\160\154\x61\157\x72\x73
прочие странные имена переменных и функций — например, длиной 40+ символов
список директорий, обращение к которым через inlclude считается криминальным.
Каждому признаку присваивается вес (веса подшаманивал вручную по ходу написания). Можно через параметр задать минимальный вес, при котором объект попадёт в отчёт.
Пример отчётаЛожных срабатываний не так много, хотя Битрикс порадовал функциями с именами рекордной длины вроде GetNumberOfWorkflowTemplatesForDocumentType()
, GetInheritedPropertyTemplateSectionMenuItems()
или языковой константы ProgressDialogWaitingForResponseFromServerText
. А я ещё удивлялся длинным путям в структуре директорий! Создатели Битрикс явно не ленятся печатать! Ну и встроить картинку в css, css в html, html в php — для них нормально.
Чтобы их исключить, предусмотрен список исключений по имени и пути файла — да, очень ненадёжно, но от идеи сравнивать crc32 я пока отказался, хотя это было бы сильно лучше. Потому что количество использования подозрительных факторов зашкаливает:
Исключения всё равно выводятся в списке — на всякий случай их тоже надо просматриватьРезультаты
Потратил день, вычищая всё найденное сканером. Вирусная активность с тех пор прекратилась, сайты снова живы. Google и Яндекс успели проиндексировать выдаваемый вирусом контент — какую-то рекламу курток на японском языке, но сейчас всё возвращается обратно.
Сканер я запустил ещё и у себя на хостинге, и тоже нашёл там куски вируса в паре файлов — благо, в давно неактивных сайтах 2016 года. Но сам факт удивил. Попробуйте у себя, если у вас есть что-то из legacy-сайтов на Битрикс / Joomla / WordPress, любопытно, найдется ли что-то!
Прикинул, сколько времени и нервов может сэкономить эта поделка, и решил поделиться ей с общественностью. Хотя когда представляю, сколько помидоров может прилететь за качество кода на нашем серьёзном Хабре — карма съёживается. А и пусть :-)
GitHub
https://github.com/mSnus/simple-virus-scanner
Дополнения и исправления приветствуются. В планах — сделать нормальную сортировку и фильтрацию результатов на JS, улучшить анализ исключений. Ну и, конечно, пригодятся новые «факторы риска» — делитесь, если придумаете таковые.