Проблемы третьего мира
Вообще на этом фриланс-проекте я занимался вёрсткой - простенький дизайн в 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"
<RequireAll>
Require all granted
Require not ip 100.20.0.0
Require not ip 100.24.0.0
#... 3000+ адресов....
</RequireAll>
А дальше пришлось писать утилиту, которая бы помогла бы мне обуздать эту гидру. Хотелось сделать быстро, одним файлом, без использования внешних зависимостей, чтобы его было легко просто скопировать в нужное место и запустить, из командной строки или через веб.
Кто первый VSCode запустит, тот и программист
В общем, эта поделка вряд ли порадует кого-то хорошим исходником или правильным программированием, зато она работает и делает то, для чего предназначена - сканирует все файлы на предмет 20+ факторов опасности, взвешивает и выдаёт общую оценку опасности файла.
Признаки разные:
использования более-менее безобидных, но опасных функций
eval()
иshell_execute()
характерных паттерны обфускации кода вроде
$WUGh64382 = irmcjaowlfxc($gbVclGM7976[39]);
длинные цепочки вроде
\144\151\x73\160\154\x61\157\x72\x73
прочие странные имена переменных и функций - например, длиной 40+ символов
список директорий, обращение к которым через include считается криминальным.
Каждому признаку присваивается вес (веса подшаманивал вручную по ходу написания). Можно через параметр задать минимальный вес, при котором объект попадёт в отчёт.
Ложных срабатываний не так много, хотя Битрикс порадовал функциями с именами рекордной длины вроде GetNumberOfWorkflowTemplatesForDocumentType()
, GetInheritedPropertyTemplateSectionMenuItems()
или языковой константы ProgressDialogWaitingForResponseFromServerText
. А я ещё удивлялся длинным путям в структуре директорий! Создатели Битрикс явно не ленятся печатать! Ну и встроить картинку в css, css в html, html в php - для них нормально.
Чтобы их исключить, предусмотрен список исключений по имени и пути файла - да, очень ненадёжно, но от идеи сравнивать crc32 я пока отказался, хотя это было бы сильно лучше. Потому что количество использования подозрительных факторов зашкаливает:
Результаты
Потратил день, вычищая всё найденное сканером. Вирусная активность с тех пор прекратилась, сайты снова живы. Google и Яндекс успели проиндексировать выдаваемый вирусом контент - какую-то рекламу курток на японском языке, но сейчас всё возвращается обратно.
Сканер я запустил ещё и у себя на хостинге, и тоже нашёл там куски вируса в паре файлов - благо, в давно неактивных сайтах 2016 года. Но сам факт удивил. Попробуйте у себя, если у вас есть что-то из legacy-сайтов на Битрикс / Joomla / WordPress, любопытно, найдется ли что-то!
Прикинул, сколько времени и нервов может сэкономить эта поделка, и решил поделиться ей с общественностью. Хотя когда представляю, сколько помидоров может прилететь за качество кода на нашем серьёзном Хабре - карма съёживается. А и пусть :-)
GitHub
https://github.com/mSnus/simple-virus-scanner
Дополнения и исправления приветствуются. В планах - сделать нормальную сортировку и фильтрацию результатов на JS, улучшить анализ исключений. Ну и, конечно, пригодятся новые "факторы риска" - делитесь, если придумаете таковые.